##// END OF EJS Templates
revert: display full subrepo output with --dry-run...
Matt Harbison -
r24134:afed5d2e default
parent child Browse files
Show More
@@ -1,2962 +1,2962 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 import changelog
14 import changelog
15 import bookmarks
15 import bookmarks
16 import encoding
16 import encoding
17 import lock as lockmod
17 import lock as lockmod
18
18
19 def parsealiases(cmd):
19 def parsealiases(cmd):
20 return cmd.lstrip("^").split("|")
20 return cmd.lstrip("^").split("|")
21
21
22 def findpossible(cmd, table, strict=False):
22 def findpossible(cmd, table, strict=False):
23 """
23 """
24 Return cmd -> (aliases, command table entry)
24 Return cmd -> (aliases, command table entry)
25 for each matching command.
25 for each matching command.
26 Return debug commands (or their aliases) only if no normal command matches.
26 Return debug commands (or their aliases) only if no normal command matches.
27 """
27 """
28 choice = {}
28 choice = {}
29 debugchoice = {}
29 debugchoice = {}
30
30
31 if cmd in table:
31 if cmd in table:
32 # short-circuit exact matches, "log" alias beats "^log|history"
32 # short-circuit exact matches, "log" alias beats "^log|history"
33 keys = [cmd]
33 keys = [cmd]
34 else:
34 else:
35 keys = table.keys()
35 keys = table.keys()
36
36
37 for e in keys:
37 for e in keys:
38 aliases = parsealiases(e)
38 aliases = parsealiases(e)
39 found = None
39 found = None
40 if cmd in aliases:
40 if cmd in aliases:
41 found = cmd
41 found = cmd
42 elif not strict:
42 elif not strict:
43 for a in aliases:
43 for a in aliases:
44 if a.startswith(cmd):
44 if a.startswith(cmd):
45 found = a
45 found = a
46 break
46 break
47 if found is not None:
47 if found is not None:
48 if aliases[0].startswith("debug") or found.startswith("debug"):
48 if aliases[0].startswith("debug") or found.startswith("debug"):
49 debugchoice[found] = (aliases, table[e])
49 debugchoice[found] = (aliases, table[e])
50 else:
50 else:
51 choice[found] = (aliases, table[e])
51 choice[found] = (aliases, table[e])
52
52
53 if not choice and debugchoice:
53 if not choice and debugchoice:
54 choice = debugchoice
54 choice = debugchoice
55
55
56 return choice
56 return choice
57
57
58 def findcmd(cmd, table, strict=True):
58 def findcmd(cmd, table, strict=True):
59 """Return (aliases, command table entry) for command string."""
59 """Return (aliases, command table entry) for command string."""
60 choice = findpossible(cmd, table, strict)
60 choice = findpossible(cmd, table, strict)
61
61
62 if cmd in choice:
62 if cmd in choice:
63 return choice[cmd]
63 return choice[cmd]
64
64
65 if len(choice) > 1:
65 if len(choice) > 1:
66 clist = choice.keys()
66 clist = choice.keys()
67 clist.sort()
67 clist.sort()
68 raise error.AmbiguousCommand(cmd, clist)
68 raise error.AmbiguousCommand(cmd, clist)
69
69
70 if choice:
70 if choice:
71 return choice.values()[0]
71 return choice.values()[0]
72
72
73 raise error.UnknownCommand(cmd)
73 raise error.UnknownCommand(cmd)
74
74
75 def findrepo(p):
75 def findrepo(p):
76 while not os.path.isdir(os.path.join(p, ".hg")):
76 while not os.path.isdir(os.path.join(p, ".hg")):
77 oldp, p = p, os.path.dirname(p)
77 oldp, p = p, os.path.dirname(p)
78 if p == oldp:
78 if p == oldp:
79 return None
79 return None
80
80
81 return p
81 return p
82
82
83 def bailifchanged(repo):
83 def bailifchanged(repo):
84 if repo.dirstate.p2() != nullid:
84 if repo.dirstate.p2() != nullid:
85 raise util.Abort(_('outstanding uncommitted merge'))
85 raise util.Abort(_('outstanding uncommitted merge'))
86 modified, added, removed, deleted = repo.status()[:4]
86 modified, added, removed, deleted = repo.status()[:4]
87 if modified or added or removed or deleted:
87 if modified or added or removed or deleted:
88 raise util.Abort(_('uncommitted changes'))
88 raise util.Abort(_('uncommitted changes'))
89 ctx = repo[None]
89 ctx = repo[None]
90 for s in sorted(ctx.substate):
90 for s in sorted(ctx.substate):
91 if ctx.sub(s).dirty():
91 if ctx.sub(s).dirty():
92 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
93
93
94 def logmessage(ui, opts):
94 def logmessage(ui, opts):
95 """ get the log message according to -m and -l option """
95 """ get the log message according to -m and -l option """
96 message = opts.get('message')
96 message = opts.get('message')
97 logfile = opts.get('logfile')
97 logfile = opts.get('logfile')
98
98
99 if message and logfile:
99 if message and logfile:
100 raise util.Abort(_('options --message and --logfile are mutually '
100 raise util.Abort(_('options --message and --logfile are mutually '
101 'exclusive'))
101 'exclusive'))
102 if not message and logfile:
102 if not message and logfile:
103 try:
103 try:
104 if logfile == '-':
104 if logfile == '-':
105 message = ui.fin.read()
105 message = ui.fin.read()
106 else:
106 else:
107 message = '\n'.join(util.readfile(logfile).splitlines())
107 message = '\n'.join(util.readfile(logfile).splitlines())
108 except IOError, inst:
108 except IOError, inst:
109 raise util.Abort(_("can't read commit message '%s': %s") %
109 raise util.Abort(_("can't read commit message '%s': %s") %
110 (logfile, inst.strerror))
110 (logfile, inst.strerror))
111 return message
111 return message
112
112
113 def mergeeditform(ctxorbool, baseform):
113 def mergeeditform(ctxorbool, baseform):
114 """build appropriate editform from ctxorbool and baseform
114 """build appropriate editform from ctxorbool and baseform
115
115
116 'ctxorbool' is one of a ctx to be committed, or a bool whether
116 'ctxorbool' is one of a ctx to be committed, or a bool whether
117 merging is committed.
117 merging is committed.
118
118
119 This returns editform 'baseform' with '.merge' if merging is
119 This returns editform 'baseform' with '.merge' if merging is
120 committed, or one with '.normal' suffix otherwise.
120 committed, or one with '.normal' suffix otherwise.
121 """
121 """
122 if isinstance(ctxorbool, bool):
122 if isinstance(ctxorbool, bool):
123 if ctxorbool:
123 if ctxorbool:
124 return baseform + ".merge"
124 return baseform + ".merge"
125 elif 1 < len(ctxorbool.parents()):
125 elif 1 < len(ctxorbool.parents()):
126 return baseform + ".merge"
126 return baseform + ".merge"
127
127
128 return baseform + ".normal"
128 return baseform + ".normal"
129
129
130 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
130 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
131 editform='', **opts):
131 editform='', **opts):
132 """get appropriate commit message editor according to '--edit' option
132 """get appropriate commit message editor according to '--edit' option
133
133
134 'finishdesc' is a function to be called with edited commit message
134 'finishdesc' is a function to be called with edited commit message
135 (= 'description' of the new changeset) just after editing, but
135 (= 'description' of the new changeset) just after editing, but
136 before checking empty-ness. It should return actual text to be
136 before checking empty-ness. It should return actual text to be
137 stored into history. This allows to change description before
137 stored into history. This allows to change description before
138 storing.
138 storing.
139
139
140 'extramsg' is a extra message to be shown in the editor instead of
140 'extramsg' is a extra message to be shown in the editor instead of
141 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
141 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
142 is automatically added.
142 is automatically added.
143
143
144 'editform' is a dot-separated list of names, to distinguish
144 'editform' is a dot-separated list of names, to distinguish
145 the purpose of commit text editing.
145 the purpose of commit text editing.
146
146
147 'getcommiteditor' returns 'commitforceeditor' regardless of
147 'getcommiteditor' returns 'commitforceeditor' regardless of
148 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
148 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
149 they are specific for usage in MQ.
149 they are specific for usage in MQ.
150 """
150 """
151 if edit or finishdesc or extramsg:
151 if edit or finishdesc or extramsg:
152 return lambda r, c, s: commitforceeditor(r, c, s,
152 return lambda r, c, s: commitforceeditor(r, c, s,
153 finishdesc=finishdesc,
153 finishdesc=finishdesc,
154 extramsg=extramsg,
154 extramsg=extramsg,
155 editform=editform)
155 editform=editform)
156 elif editform:
156 elif editform:
157 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
157 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
158 else:
158 else:
159 return commiteditor
159 return commiteditor
160
160
161 def loglimit(opts):
161 def loglimit(opts):
162 """get the log limit according to option -l/--limit"""
162 """get the log limit according to option -l/--limit"""
163 limit = opts.get('limit')
163 limit = opts.get('limit')
164 if limit:
164 if limit:
165 try:
165 try:
166 limit = int(limit)
166 limit = int(limit)
167 except ValueError:
167 except ValueError:
168 raise util.Abort(_('limit must be a positive integer'))
168 raise util.Abort(_('limit must be a positive integer'))
169 if limit <= 0:
169 if limit <= 0:
170 raise util.Abort(_('limit must be positive'))
170 raise util.Abort(_('limit must be positive'))
171 else:
171 else:
172 limit = None
172 limit = None
173 return limit
173 return limit
174
174
175 def makefilename(repo, pat, node, desc=None,
175 def makefilename(repo, pat, node, desc=None,
176 total=None, seqno=None, revwidth=None, pathname=None):
176 total=None, seqno=None, revwidth=None, pathname=None):
177 node_expander = {
177 node_expander = {
178 'H': lambda: hex(node),
178 'H': lambda: hex(node),
179 'R': lambda: str(repo.changelog.rev(node)),
179 'R': lambda: str(repo.changelog.rev(node)),
180 'h': lambda: short(node),
180 'h': lambda: short(node),
181 'm': lambda: re.sub('[^\w]', '_', str(desc))
181 'm': lambda: re.sub('[^\w]', '_', str(desc))
182 }
182 }
183 expander = {
183 expander = {
184 '%': lambda: '%',
184 '%': lambda: '%',
185 'b': lambda: os.path.basename(repo.root),
185 'b': lambda: os.path.basename(repo.root),
186 }
186 }
187
187
188 try:
188 try:
189 if node:
189 if node:
190 expander.update(node_expander)
190 expander.update(node_expander)
191 if node:
191 if node:
192 expander['r'] = (lambda:
192 expander['r'] = (lambda:
193 str(repo.changelog.rev(node)).zfill(revwidth or 0))
193 str(repo.changelog.rev(node)).zfill(revwidth or 0))
194 if total is not None:
194 if total is not None:
195 expander['N'] = lambda: str(total)
195 expander['N'] = lambda: str(total)
196 if seqno is not None:
196 if seqno is not None:
197 expander['n'] = lambda: str(seqno)
197 expander['n'] = lambda: str(seqno)
198 if total is not None and seqno is not None:
198 if total is not None and seqno is not None:
199 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
199 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
200 if pathname is not None:
200 if pathname is not None:
201 expander['s'] = lambda: os.path.basename(pathname)
201 expander['s'] = lambda: os.path.basename(pathname)
202 expander['d'] = lambda: os.path.dirname(pathname) or '.'
202 expander['d'] = lambda: os.path.dirname(pathname) or '.'
203 expander['p'] = lambda: pathname
203 expander['p'] = lambda: pathname
204
204
205 newname = []
205 newname = []
206 patlen = len(pat)
206 patlen = len(pat)
207 i = 0
207 i = 0
208 while i < patlen:
208 while i < patlen:
209 c = pat[i]
209 c = pat[i]
210 if c == '%':
210 if c == '%':
211 i += 1
211 i += 1
212 c = pat[i]
212 c = pat[i]
213 c = expander[c]()
213 c = expander[c]()
214 newname.append(c)
214 newname.append(c)
215 i += 1
215 i += 1
216 return ''.join(newname)
216 return ''.join(newname)
217 except KeyError, inst:
217 except KeyError, inst:
218 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
218 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
219 inst.args[0])
219 inst.args[0])
220
220
221 def makefileobj(repo, pat, node=None, desc=None, total=None,
221 def makefileobj(repo, pat, node=None, desc=None, total=None,
222 seqno=None, revwidth=None, mode='wb', modemap=None,
222 seqno=None, revwidth=None, mode='wb', modemap=None,
223 pathname=None):
223 pathname=None):
224
224
225 writable = mode not in ('r', 'rb')
225 writable = mode not in ('r', 'rb')
226
226
227 if not pat or pat == '-':
227 if not pat or pat == '-':
228 fp = writable and repo.ui.fout or repo.ui.fin
228 fp = writable and repo.ui.fout or repo.ui.fin
229 if util.safehasattr(fp, 'fileno'):
229 if util.safehasattr(fp, 'fileno'):
230 return os.fdopen(os.dup(fp.fileno()), mode)
230 return os.fdopen(os.dup(fp.fileno()), mode)
231 else:
231 else:
232 # if this fp can't be duped properly, return
232 # if this fp can't be duped properly, return
233 # a dummy object that can be closed
233 # a dummy object that can be closed
234 class wrappedfileobj(object):
234 class wrappedfileobj(object):
235 noop = lambda x: None
235 noop = lambda x: None
236 def __init__(self, f):
236 def __init__(self, f):
237 self.f = f
237 self.f = f
238 def __getattr__(self, attr):
238 def __getattr__(self, attr):
239 if attr == 'close':
239 if attr == 'close':
240 return self.noop
240 return self.noop
241 else:
241 else:
242 return getattr(self.f, attr)
242 return getattr(self.f, attr)
243
243
244 return wrappedfileobj(fp)
244 return wrappedfileobj(fp)
245 if util.safehasattr(pat, 'write') and writable:
245 if util.safehasattr(pat, 'write') and writable:
246 return pat
246 return pat
247 if util.safehasattr(pat, 'read') and 'r' in mode:
247 if util.safehasattr(pat, 'read') and 'r' in mode:
248 return pat
248 return pat
249 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
249 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
250 if modemap is not None:
250 if modemap is not None:
251 mode = modemap.get(fn, mode)
251 mode = modemap.get(fn, mode)
252 if mode == 'wb':
252 if mode == 'wb':
253 modemap[fn] = 'ab'
253 modemap[fn] = 'ab'
254 return open(fn, mode)
254 return open(fn, mode)
255
255
256 def openrevlog(repo, cmd, file_, opts):
256 def openrevlog(repo, cmd, file_, opts):
257 """opens the changelog, manifest, a filelog or a given revlog"""
257 """opens the changelog, manifest, a filelog or a given revlog"""
258 cl = opts['changelog']
258 cl = opts['changelog']
259 mf = opts['manifest']
259 mf = opts['manifest']
260 msg = None
260 msg = None
261 if cl and mf:
261 if cl and mf:
262 msg = _('cannot specify --changelog and --manifest at the same time')
262 msg = _('cannot specify --changelog and --manifest at the same time')
263 elif cl or mf:
263 elif cl or mf:
264 if file_:
264 if file_:
265 msg = _('cannot specify filename with --changelog or --manifest')
265 msg = _('cannot specify filename with --changelog or --manifest')
266 elif not repo:
266 elif not repo:
267 msg = _('cannot specify --changelog or --manifest '
267 msg = _('cannot specify --changelog or --manifest '
268 'without a repository')
268 'without a repository')
269 if msg:
269 if msg:
270 raise util.Abort(msg)
270 raise util.Abort(msg)
271
271
272 r = None
272 r = None
273 if repo:
273 if repo:
274 if cl:
274 if cl:
275 r = repo.unfiltered().changelog
275 r = repo.unfiltered().changelog
276 elif mf:
276 elif mf:
277 r = repo.manifest
277 r = repo.manifest
278 elif file_:
278 elif file_:
279 filelog = repo.file(file_)
279 filelog = repo.file(file_)
280 if len(filelog):
280 if len(filelog):
281 r = filelog
281 r = filelog
282 if not r:
282 if not r:
283 if not file_:
283 if not file_:
284 raise error.CommandError(cmd, _('invalid arguments'))
284 raise error.CommandError(cmd, _('invalid arguments'))
285 if not os.path.isfile(file_):
285 if not os.path.isfile(file_):
286 raise util.Abort(_("revlog '%s' not found") % file_)
286 raise util.Abort(_("revlog '%s' not found") % file_)
287 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
287 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
288 file_[:-2] + ".i")
288 file_[:-2] + ".i")
289 return r
289 return r
290
290
291 def copy(ui, repo, pats, opts, rename=False):
291 def copy(ui, repo, pats, opts, rename=False):
292 # called with the repo lock held
292 # called with the repo lock held
293 #
293 #
294 # hgsep => pathname that uses "/" to separate directories
294 # hgsep => pathname that uses "/" to separate directories
295 # ossep => pathname that uses os.sep to separate directories
295 # ossep => pathname that uses os.sep to separate directories
296 cwd = repo.getcwd()
296 cwd = repo.getcwd()
297 targets = {}
297 targets = {}
298 after = opts.get("after")
298 after = opts.get("after")
299 dryrun = opts.get("dry_run")
299 dryrun = opts.get("dry_run")
300 wctx = repo[None]
300 wctx = repo[None]
301
301
302 def walkpat(pat):
302 def walkpat(pat):
303 srcs = []
303 srcs = []
304 badstates = after and '?' or '?r'
304 badstates = after and '?' or '?r'
305 m = scmutil.match(repo[None], [pat], opts, globbed=True)
305 m = scmutil.match(repo[None], [pat], opts, globbed=True)
306 for abs in repo.walk(m):
306 for abs in repo.walk(m):
307 state = repo.dirstate[abs]
307 state = repo.dirstate[abs]
308 rel = m.rel(abs)
308 rel = m.rel(abs)
309 exact = m.exact(abs)
309 exact = m.exact(abs)
310 if state in badstates:
310 if state in badstates:
311 if exact and state == '?':
311 if exact and state == '?':
312 ui.warn(_('%s: not copying - file is not managed\n') % rel)
312 ui.warn(_('%s: not copying - file is not managed\n') % rel)
313 if exact and state == 'r':
313 if exact and state == 'r':
314 ui.warn(_('%s: not copying - file has been marked for'
314 ui.warn(_('%s: not copying - file has been marked for'
315 ' remove\n') % rel)
315 ' remove\n') % rel)
316 continue
316 continue
317 # abs: hgsep
317 # abs: hgsep
318 # rel: ossep
318 # rel: ossep
319 srcs.append((abs, rel, exact))
319 srcs.append((abs, rel, exact))
320 return srcs
320 return srcs
321
321
322 # abssrc: hgsep
322 # abssrc: hgsep
323 # relsrc: ossep
323 # relsrc: ossep
324 # otarget: ossep
324 # otarget: ossep
325 def copyfile(abssrc, relsrc, otarget, exact):
325 def copyfile(abssrc, relsrc, otarget, exact):
326 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
326 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
327 if '/' in abstarget:
327 if '/' in abstarget:
328 # We cannot normalize abstarget itself, this would prevent
328 # We cannot normalize abstarget itself, this would prevent
329 # case only renames, like a => A.
329 # case only renames, like a => A.
330 abspath, absname = abstarget.rsplit('/', 1)
330 abspath, absname = abstarget.rsplit('/', 1)
331 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
331 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
332 reltarget = repo.pathto(abstarget, cwd)
332 reltarget = repo.pathto(abstarget, cwd)
333 target = repo.wjoin(abstarget)
333 target = repo.wjoin(abstarget)
334 src = repo.wjoin(abssrc)
334 src = repo.wjoin(abssrc)
335 state = repo.dirstate[abstarget]
335 state = repo.dirstate[abstarget]
336
336
337 scmutil.checkportable(ui, abstarget)
337 scmutil.checkportable(ui, abstarget)
338
338
339 # check for collisions
339 # check for collisions
340 prevsrc = targets.get(abstarget)
340 prevsrc = targets.get(abstarget)
341 if prevsrc is not None:
341 if prevsrc is not None:
342 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
342 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
343 (reltarget, repo.pathto(abssrc, cwd),
343 (reltarget, repo.pathto(abssrc, cwd),
344 repo.pathto(prevsrc, cwd)))
344 repo.pathto(prevsrc, cwd)))
345 return
345 return
346
346
347 # check for overwrites
347 # check for overwrites
348 exists = os.path.lexists(target)
348 exists = os.path.lexists(target)
349 samefile = False
349 samefile = False
350 if exists and abssrc != abstarget:
350 if exists and abssrc != abstarget:
351 if (repo.dirstate.normalize(abssrc) ==
351 if (repo.dirstate.normalize(abssrc) ==
352 repo.dirstate.normalize(abstarget)):
352 repo.dirstate.normalize(abstarget)):
353 if not rename:
353 if not rename:
354 ui.warn(_("%s: can't copy - same file\n") % reltarget)
354 ui.warn(_("%s: can't copy - same file\n") % reltarget)
355 return
355 return
356 exists = False
356 exists = False
357 samefile = True
357 samefile = True
358
358
359 if not after and exists or after and state in 'mn':
359 if not after and exists or after and state in 'mn':
360 if not opts['force']:
360 if not opts['force']:
361 ui.warn(_('%s: not overwriting - file exists\n') %
361 ui.warn(_('%s: not overwriting - file exists\n') %
362 reltarget)
362 reltarget)
363 return
363 return
364
364
365 if after:
365 if after:
366 if not exists:
366 if not exists:
367 if rename:
367 if rename:
368 ui.warn(_('%s: not recording move - %s does not exist\n') %
368 ui.warn(_('%s: not recording move - %s does not exist\n') %
369 (relsrc, reltarget))
369 (relsrc, reltarget))
370 else:
370 else:
371 ui.warn(_('%s: not recording copy - %s does not exist\n') %
371 ui.warn(_('%s: not recording copy - %s does not exist\n') %
372 (relsrc, reltarget))
372 (relsrc, reltarget))
373 return
373 return
374 elif not dryrun:
374 elif not dryrun:
375 try:
375 try:
376 if exists:
376 if exists:
377 os.unlink(target)
377 os.unlink(target)
378 targetdir = os.path.dirname(target) or '.'
378 targetdir = os.path.dirname(target) or '.'
379 if not os.path.isdir(targetdir):
379 if not os.path.isdir(targetdir):
380 os.makedirs(targetdir)
380 os.makedirs(targetdir)
381 if samefile:
381 if samefile:
382 tmp = target + "~hgrename"
382 tmp = target + "~hgrename"
383 os.rename(src, tmp)
383 os.rename(src, tmp)
384 os.rename(tmp, target)
384 os.rename(tmp, target)
385 else:
385 else:
386 util.copyfile(src, target)
386 util.copyfile(src, target)
387 srcexists = True
387 srcexists = True
388 except IOError, inst:
388 except IOError, inst:
389 if inst.errno == errno.ENOENT:
389 if inst.errno == errno.ENOENT:
390 ui.warn(_('%s: deleted in working copy\n') % relsrc)
390 ui.warn(_('%s: deleted in working copy\n') % relsrc)
391 srcexists = False
391 srcexists = False
392 else:
392 else:
393 ui.warn(_('%s: cannot copy - %s\n') %
393 ui.warn(_('%s: cannot copy - %s\n') %
394 (relsrc, inst.strerror))
394 (relsrc, inst.strerror))
395 return True # report a failure
395 return True # report a failure
396
396
397 if ui.verbose or not exact:
397 if ui.verbose or not exact:
398 if rename:
398 if rename:
399 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
399 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
400 else:
400 else:
401 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
401 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
402
402
403 targets[abstarget] = abssrc
403 targets[abstarget] = abssrc
404
404
405 # fix up dirstate
405 # fix up dirstate
406 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
406 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
407 dryrun=dryrun, cwd=cwd)
407 dryrun=dryrun, cwd=cwd)
408 if rename and not dryrun:
408 if rename and not dryrun:
409 if not after and srcexists and not samefile:
409 if not after and srcexists and not samefile:
410 util.unlinkpath(repo.wjoin(abssrc))
410 util.unlinkpath(repo.wjoin(abssrc))
411 wctx.forget([abssrc])
411 wctx.forget([abssrc])
412
412
413 # pat: ossep
413 # pat: ossep
414 # dest ossep
414 # dest ossep
415 # srcs: list of (hgsep, hgsep, ossep, bool)
415 # srcs: list of (hgsep, hgsep, ossep, bool)
416 # return: function that takes hgsep and returns ossep
416 # return: function that takes hgsep and returns ossep
417 def targetpathfn(pat, dest, srcs):
417 def targetpathfn(pat, dest, srcs):
418 if os.path.isdir(pat):
418 if os.path.isdir(pat):
419 abspfx = pathutil.canonpath(repo.root, cwd, pat)
419 abspfx = pathutil.canonpath(repo.root, cwd, pat)
420 abspfx = util.localpath(abspfx)
420 abspfx = util.localpath(abspfx)
421 if destdirexists:
421 if destdirexists:
422 striplen = len(os.path.split(abspfx)[0])
422 striplen = len(os.path.split(abspfx)[0])
423 else:
423 else:
424 striplen = len(abspfx)
424 striplen = len(abspfx)
425 if striplen:
425 if striplen:
426 striplen += len(os.sep)
426 striplen += len(os.sep)
427 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
427 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
428 elif destdirexists:
428 elif destdirexists:
429 res = lambda p: os.path.join(dest,
429 res = lambda p: os.path.join(dest,
430 os.path.basename(util.localpath(p)))
430 os.path.basename(util.localpath(p)))
431 else:
431 else:
432 res = lambda p: dest
432 res = lambda p: dest
433 return res
433 return res
434
434
435 # pat: ossep
435 # pat: ossep
436 # dest ossep
436 # dest ossep
437 # srcs: list of (hgsep, hgsep, ossep, bool)
437 # srcs: list of (hgsep, hgsep, ossep, bool)
438 # return: function that takes hgsep and returns ossep
438 # return: function that takes hgsep and returns ossep
439 def targetpathafterfn(pat, dest, srcs):
439 def targetpathafterfn(pat, dest, srcs):
440 if matchmod.patkind(pat):
440 if matchmod.patkind(pat):
441 # a mercurial pattern
441 # a mercurial pattern
442 res = lambda p: os.path.join(dest,
442 res = lambda p: os.path.join(dest,
443 os.path.basename(util.localpath(p)))
443 os.path.basename(util.localpath(p)))
444 else:
444 else:
445 abspfx = pathutil.canonpath(repo.root, cwd, pat)
445 abspfx = pathutil.canonpath(repo.root, cwd, pat)
446 if len(abspfx) < len(srcs[0][0]):
446 if len(abspfx) < len(srcs[0][0]):
447 # A directory. Either the target path contains the last
447 # A directory. Either the target path contains the last
448 # component of the source path or it does not.
448 # component of the source path or it does not.
449 def evalpath(striplen):
449 def evalpath(striplen):
450 score = 0
450 score = 0
451 for s in srcs:
451 for s in srcs:
452 t = os.path.join(dest, util.localpath(s[0])[striplen:])
452 t = os.path.join(dest, util.localpath(s[0])[striplen:])
453 if os.path.lexists(t):
453 if os.path.lexists(t):
454 score += 1
454 score += 1
455 return score
455 return score
456
456
457 abspfx = util.localpath(abspfx)
457 abspfx = util.localpath(abspfx)
458 striplen = len(abspfx)
458 striplen = len(abspfx)
459 if striplen:
459 if striplen:
460 striplen += len(os.sep)
460 striplen += len(os.sep)
461 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
461 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
462 score = evalpath(striplen)
462 score = evalpath(striplen)
463 striplen1 = len(os.path.split(abspfx)[0])
463 striplen1 = len(os.path.split(abspfx)[0])
464 if striplen1:
464 if striplen1:
465 striplen1 += len(os.sep)
465 striplen1 += len(os.sep)
466 if evalpath(striplen1) > score:
466 if evalpath(striplen1) > score:
467 striplen = striplen1
467 striplen = striplen1
468 res = lambda p: os.path.join(dest,
468 res = lambda p: os.path.join(dest,
469 util.localpath(p)[striplen:])
469 util.localpath(p)[striplen:])
470 else:
470 else:
471 # a file
471 # a file
472 if destdirexists:
472 if destdirexists:
473 res = lambda p: os.path.join(dest,
473 res = lambda p: os.path.join(dest,
474 os.path.basename(util.localpath(p)))
474 os.path.basename(util.localpath(p)))
475 else:
475 else:
476 res = lambda p: dest
476 res = lambda p: dest
477 return res
477 return res
478
478
479
479
480 pats = scmutil.expandpats(pats)
480 pats = scmutil.expandpats(pats)
481 if not pats:
481 if not pats:
482 raise util.Abort(_('no source or destination specified'))
482 raise util.Abort(_('no source or destination specified'))
483 if len(pats) == 1:
483 if len(pats) == 1:
484 raise util.Abort(_('no destination specified'))
484 raise util.Abort(_('no destination specified'))
485 dest = pats.pop()
485 dest = pats.pop()
486 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
486 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
487 if not destdirexists:
487 if not destdirexists:
488 if len(pats) > 1 or matchmod.patkind(pats[0]):
488 if len(pats) > 1 or matchmod.patkind(pats[0]):
489 raise util.Abort(_('with multiple sources, destination must be an '
489 raise util.Abort(_('with multiple sources, destination must be an '
490 'existing directory'))
490 'existing directory'))
491 if util.endswithsep(dest):
491 if util.endswithsep(dest):
492 raise util.Abort(_('destination %s is not a directory') % dest)
492 raise util.Abort(_('destination %s is not a directory') % dest)
493
493
494 tfn = targetpathfn
494 tfn = targetpathfn
495 if after:
495 if after:
496 tfn = targetpathafterfn
496 tfn = targetpathafterfn
497 copylist = []
497 copylist = []
498 for pat in pats:
498 for pat in pats:
499 srcs = walkpat(pat)
499 srcs = walkpat(pat)
500 if not srcs:
500 if not srcs:
501 continue
501 continue
502 copylist.append((tfn(pat, dest, srcs), srcs))
502 copylist.append((tfn(pat, dest, srcs), srcs))
503 if not copylist:
503 if not copylist:
504 raise util.Abort(_('no files to copy'))
504 raise util.Abort(_('no files to copy'))
505
505
506 errors = 0
506 errors = 0
507 for targetpath, srcs in copylist:
507 for targetpath, srcs in copylist:
508 for abssrc, relsrc, exact in srcs:
508 for abssrc, relsrc, exact in srcs:
509 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
509 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
510 errors += 1
510 errors += 1
511
511
512 if errors:
512 if errors:
513 ui.warn(_('(consider using --after)\n'))
513 ui.warn(_('(consider using --after)\n'))
514
514
515 return errors != 0
515 return errors != 0
516
516
517 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
517 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
518 runargs=None, appendpid=False):
518 runargs=None, appendpid=False):
519 '''Run a command as a service.'''
519 '''Run a command as a service.'''
520
520
521 def writepid(pid):
521 def writepid(pid):
522 if opts['pid_file']:
522 if opts['pid_file']:
523 mode = appendpid and 'a' or 'w'
523 mode = appendpid and 'a' or 'w'
524 fp = open(opts['pid_file'], mode)
524 fp = open(opts['pid_file'], mode)
525 fp.write(str(pid) + '\n')
525 fp.write(str(pid) + '\n')
526 fp.close()
526 fp.close()
527
527
528 if opts['daemon'] and not opts['daemon_pipefds']:
528 if opts['daemon'] and not opts['daemon_pipefds']:
529 # Signal child process startup with file removal
529 # Signal child process startup with file removal
530 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
530 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
531 os.close(lockfd)
531 os.close(lockfd)
532 try:
532 try:
533 if not runargs:
533 if not runargs:
534 runargs = util.hgcmd() + sys.argv[1:]
534 runargs = util.hgcmd() + sys.argv[1:]
535 runargs.append('--daemon-pipefds=%s' % lockpath)
535 runargs.append('--daemon-pipefds=%s' % lockpath)
536 # Don't pass --cwd to the child process, because we've already
536 # Don't pass --cwd to the child process, because we've already
537 # changed directory.
537 # changed directory.
538 for i in xrange(1, len(runargs)):
538 for i in xrange(1, len(runargs)):
539 if runargs[i].startswith('--cwd='):
539 if runargs[i].startswith('--cwd='):
540 del runargs[i]
540 del runargs[i]
541 break
541 break
542 elif runargs[i].startswith('--cwd'):
542 elif runargs[i].startswith('--cwd'):
543 del runargs[i:i + 2]
543 del runargs[i:i + 2]
544 break
544 break
545 def condfn():
545 def condfn():
546 return not os.path.exists(lockpath)
546 return not os.path.exists(lockpath)
547 pid = util.rundetached(runargs, condfn)
547 pid = util.rundetached(runargs, condfn)
548 if pid < 0:
548 if pid < 0:
549 raise util.Abort(_('child process failed to start'))
549 raise util.Abort(_('child process failed to start'))
550 writepid(pid)
550 writepid(pid)
551 finally:
551 finally:
552 try:
552 try:
553 os.unlink(lockpath)
553 os.unlink(lockpath)
554 except OSError, e:
554 except OSError, e:
555 if e.errno != errno.ENOENT:
555 if e.errno != errno.ENOENT:
556 raise
556 raise
557 if parentfn:
557 if parentfn:
558 return parentfn(pid)
558 return parentfn(pid)
559 else:
559 else:
560 return
560 return
561
561
562 if initfn:
562 if initfn:
563 initfn()
563 initfn()
564
564
565 if not opts['daemon']:
565 if not opts['daemon']:
566 writepid(os.getpid())
566 writepid(os.getpid())
567
567
568 if opts['daemon_pipefds']:
568 if opts['daemon_pipefds']:
569 lockpath = opts['daemon_pipefds']
569 lockpath = opts['daemon_pipefds']
570 try:
570 try:
571 os.setsid()
571 os.setsid()
572 except AttributeError:
572 except AttributeError:
573 pass
573 pass
574 os.unlink(lockpath)
574 os.unlink(lockpath)
575 util.hidewindow()
575 util.hidewindow()
576 sys.stdout.flush()
576 sys.stdout.flush()
577 sys.stderr.flush()
577 sys.stderr.flush()
578
578
579 nullfd = os.open(os.devnull, os.O_RDWR)
579 nullfd = os.open(os.devnull, os.O_RDWR)
580 logfilefd = nullfd
580 logfilefd = nullfd
581 if logfile:
581 if logfile:
582 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
582 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
583 os.dup2(nullfd, 0)
583 os.dup2(nullfd, 0)
584 os.dup2(logfilefd, 1)
584 os.dup2(logfilefd, 1)
585 os.dup2(logfilefd, 2)
585 os.dup2(logfilefd, 2)
586 if nullfd not in (0, 1, 2):
586 if nullfd not in (0, 1, 2):
587 os.close(nullfd)
587 os.close(nullfd)
588 if logfile and logfilefd not in (0, 1, 2):
588 if logfile and logfilefd not in (0, 1, 2):
589 os.close(logfilefd)
589 os.close(logfilefd)
590
590
591 if runfn:
591 if runfn:
592 return runfn()
592 return runfn()
593
593
594 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
594 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
595 """Utility function used by commands.import to import a single patch
595 """Utility function used by commands.import to import a single patch
596
596
597 This function is explicitly defined here to help the evolve extension to
597 This function is explicitly defined here to help the evolve extension to
598 wrap this part of the import logic.
598 wrap this part of the import logic.
599
599
600 The API is currently a bit ugly because it a simple code translation from
600 The API is currently a bit ugly because it a simple code translation from
601 the import command. Feel free to make it better.
601 the import command. Feel free to make it better.
602
602
603 :hunk: a patch (as a binary string)
603 :hunk: a patch (as a binary string)
604 :parents: nodes that will be parent of the created commit
604 :parents: nodes that will be parent of the created commit
605 :opts: the full dict of option passed to the import command
605 :opts: the full dict of option passed to the import command
606 :msgs: list to save commit message to.
606 :msgs: list to save commit message to.
607 (used in case we need to save it when failing)
607 (used in case we need to save it when failing)
608 :updatefunc: a function that update a repo to a given node
608 :updatefunc: a function that update a repo to a given node
609 updatefunc(<repo>, <node>)
609 updatefunc(<repo>, <node>)
610 """
610 """
611 tmpname, message, user, date, branch, nodeid, p1, p2 = \
611 tmpname, message, user, date, branch, nodeid, p1, p2 = \
612 patch.extract(ui, hunk)
612 patch.extract(ui, hunk)
613
613
614 update = not opts.get('bypass')
614 update = not opts.get('bypass')
615 strip = opts["strip"]
615 strip = opts["strip"]
616 sim = float(opts.get('similarity') or 0)
616 sim = float(opts.get('similarity') or 0)
617 if not tmpname:
617 if not tmpname:
618 return (None, None, False)
618 return (None, None, False)
619 msg = _('applied to working directory')
619 msg = _('applied to working directory')
620
620
621 rejects = False
621 rejects = False
622
622
623 try:
623 try:
624 cmdline_message = logmessage(ui, opts)
624 cmdline_message = logmessage(ui, opts)
625 if cmdline_message:
625 if cmdline_message:
626 # pickup the cmdline msg
626 # pickup the cmdline msg
627 message = cmdline_message
627 message = cmdline_message
628 elif message:
628 elif message:
629 # pickup the patch msg
629 # pickup the patch msg
630 message = message.strip()
630 message = message.strip()
631 else:
631 else:
632 # launch the editor
632 # launch the editor
633 message = None
633 message = None
634 ui.debug('message:\n%s\n' % message)
634 ui.debug('message:\n%s\n' % message)
635
635
636 if len(parents) == 1:
636 if len(parents) == 1:
637 parents.append(repo[nullid])
637 parents.append(repo[nullid])
638 if opts.get('exact'):
638 if opts.get('exact'):
639 if not nodeid or not p1:
639 if not nodeid or not p1:
640 raise util.Abort(_('not a Mercurial patch'))
640 raise util.Abort(_('not a Mercurial patch'))
641 p1 = repo[p1]
641 p1 = repo[p1]
642 p2 = repo[p2 or nullid]
642 p2 = repo[p2 or nullid]
643 elif p2:
643 elif p2:
644 try:
644 try:
645 p1 = repo[p1]
645 p1 = repo[p1]
646 p2 = repo[p2]
646 p2 = repo[p2]
647 # Without any options, consider p2 only if the
647 # Without any options, consider p2 only if the
648 # patch is being applied on top of the recorded
648 # patch is being applied on top of the recorded
649 # first parent.
649 # first parent.
650 if p1 != parents[0]:
650 if p1 != parents[0]:
651 p1 = parents[0]
651 p1 = parents[0]
652 p2 = repo[nullid]
652 p2 = repo[nullid]
653 except error.RepoError:
653 except error.RepoError:
654 p1, p2 = parents
654 p1, p2 = parents
655 if p2.node() == nullid:
655 if p2.node() == nullid:
656 ui.warn(_("warning: import the patch as a normal revision\n"
656 ui.warn(_("warning: import the patch as a normal revision\n"
657 "(use --exact to import the patch as a merge)\n"))
657 "(use --exact to import the patch as a merge)\n"))
658 else:
658 else:
659 p1, p2 = parents
659 p1, p2 = parents
660
660
661 n = None
661 n = None
662 if update:
662 if update:
663 repo.dirstate.beginparentchange()
663 repo.dirstate.beginparentchange()
664 if p1 != parents[0]:
664 if p1 != parents[0]:
665 updatefunc(repo, p1.node())
665 updatefunc(repo, p1.node())
666 if p2 != parents[1]:
666 if p2 != parents[1]:
667 repo.setparents(p1.node(), p2.node())
667 repo.setparents(p1.node(), p2.node())
668
668
669 if opts.get('exact') or opts.get('import_branch'):
669 if opts.get('exact') or opts.get('import_branch'):
670 repo.dirstate.setbranch(branch or 'default')
670 repo.dirstate.setbranch(branch or 'default')
671
671
672 partial = opts.get('partial', False)
672 partial = opts.get('partial', False)
673 files = set()
673 files = set()
674 try:
674 try:
675 patch.patch(ui, repo, tmpname, strip=strip, files=files,
675 patch.patch(ui, repo, tmpname, strip=strip, files=files,
676 eolmode=None, similarity=sim / 100.0)
676 eolmode=None, similarity=sim / 100.0)
677 except patch.PatchError, e:
677 except patch.PatchError, e:
678 if not partial:
678 if not partial:
679 raise util.Abort(str(e))
679 raise util.Abort(str(e))
680 if partial:
680 if partial:
681 rejects = True
681 rejects = True
682
682
683 files = list(files)
683 files = list(files)
684 if opts.get('no_commit'):
684 if opts.get('no_commit'):
685 if message:
685 if message:
686 msgs.append(message)
686 msgs.append(message)
687 else:
687 else:
688 if opts.get('exact') or p2:
688 if opts.get('exact') or p2:
689 # If you got here, you either use --force and know what
689 # If you got here, you either use --force and know what
690 # you are doing or used --exact or a merge patch while
690 # you are doing or used --exact or a merge patch while
691 # being updated to its first parent.
691 # being updated to its first parent.
692 m = None
692 m = None
693 else:
693 else:
694 m = scmutil.matchfiles(repo, files or [])
694 m = scmutil.matchfiles(repo, files or [])
695 editform = mergeeditform(repo[None], 'import.normal')
695 editform = mergeeditform(repo[None], 'import.normal')
696 if opts.get('exact'):
696 if opts.get('exact'):
697 editor = None
697 editor = None
698 else:
698 else:
699 editor = getcommiteditor(editform=editform, **opts)
699 editor = getcommiteditor(editform=editform, **opts)
700 n = repo.commit(message, opts.get('user') or user,
700 n = repo.commit(message, opts.get('user') or user,
701 opts.get('date') or date, match=m,
701 opts.get('date') or date, match=m,
702 editor=editor, force=partial)
702 editor=editor, force=partial)
703 repo.dirstate.endparentchange()
703 repo.dirstate.endparentchange()
704 else:
704 else:
705 if opts.get('exact') or opts.get('import_branch'):
705 if opts.get('exact') or opts.get('import_branch'):
706 branch = branch or 'default'
706 branch = branch or 'default'
707 else:
707 else:
708 branch = p1.branch()
708 branch = p1.branch()
709 store = patch.filestore()
709 store = patch.filestore()
710 try:
710 try:
711 files = set()
711 files = set()
712 try:
712 try:
713 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
713 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
714 files, eolmode=None)
714 files, eolmode=None)
715 except patch.PatchError, e:
715 except patch.PatchError, e:
716 raise util.Abort(str(e))
716 raise util.Abort(str(e))
717 if opts.get('exact'):
717 if opts.get('exact'):
718 editor = None
718 editor = None
719 else:
719 else:
720 editor = getcommiteditor(editform='import.bypass')
720 editor = getcommiteditor(editform='import.bypass')
721 memctx = context.makememctx(repo, (p1.node(), p2.node()),
721 memctx = context.makememctx(repo, (p1.node(), p2.node()),
722 message,
722 message,
723 opts.get('user') or user,
723 opts.get('user') or user,
724 opts.get('date') or date,
724 opts.get('date') or date,
725 branch, files, store,
725 branch, files, store,
726 editor=editor)
726 editor=editor)
727 n = memctx.commit()
727 n = memctx.commit()
728 finally:
728 finally:
729 store.close()
729 store.close()
730 if opts.get('exact') and opts.get('no_commit'):
730 if opts.get('exact') and opts.get('no_commit'):
731 # --exact with --no-commit is still useful in that it does merge
731 # --exact with --no-commit is still useful in that it does merge
732 # and branch bits
732 # and branch bits
733 ui.warn(_("warning: can't check exact import with --no-commit\n"))
733 ui.warn(_("warning: can't check exact import with --no-commit\n"))
734 elif opts.get('exact') and hex(n) != nodeid:
734 elif opts.get('exact') and hex(n) != nodeid:
735 raise util.Abort(_('patch is damaged or loses information'))
735 raise util.Abort(_('patch is damaged or loses information'))
736 if n:
736 if n:
737 # i18n: refers to a short changeset id
737 # i18n: refers to a short changeset id
738 msg = _('created %s') % short(n)
738 msg = _('created %s') % short(n)
739 return (msg, n, rejects)
739 return (msg, n, rejects)
740 finally:
740 finally:
741 os.unlink(tmpname)
741 os.unlink(tmpname)
742
742
743 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
743 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
744 opts=None):
744 opts=None):
745 '''export changesets as hg patches.'''
745 '''export changesets as hg patches.'''
746
746
747 total = len(revs)
747 total = len(revs)
748 revwidth = max([len(str(rev)) for rev in revs])
748 revwidth = max([len(str(rev)) for rev in revs])
749 filemode = {}
749 filemode = {}
750
750
751 def single(rev, seqno, fp):
751 def single(rev, seqno, fp):
752 ctx = repo[rev]
752 ctx = repo[rev]
753 node = ctx.node()
753 node = ctx.node()
754 parents = [p.node() for p in ctx.parents() if p]
754 parents = [p.node() for p in ctx.parents() if p]
755 branch = ctx.branch()
755 branch = ctx.branch()
756 if switch_parent:
756 if switch_parent:
757 parents.reverse()
757 parents.reverse()
758 prev = (parents and parents[0]) or nullid
758 prev = (parents and parents[0]) or nullid
759
759
760 shouldclose = False
760 shouldclose = False
761 if not fp and len(template) > 0:
761 if not fp and len(template) > 0:
762 desc_lines = ctx.description().rstrip().split('\n')
762 desc_lines = ctx.description().rstrip().split('\n')
763 desc = desc_lines[0] #Commit always has a first line.
763 desc = desc_lines[0] #Commit always has a first line.
764 fp = makefileobj(repo, template, node, desc=desc, total=total,
764 fp = makefileobj(repo, template, node, desc=desc, total=total,
765 seqno=seqno, revwidth=revwidth, mode='wb',
765 seqno=seqno, revwidth=revwidth, mode='wb',
766 modemap=filemode)
766 modemap=filemode)
767 if fp != template:
767 if fp != template:
768 shouldclose = True
768 shouldclose = True
769 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
769 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
770 repo.ui.note("%s\n" % fp.name)
770 repo.ui.note("%s\n" % fp.name)
771
771
772 if not fp:
772 if not fp:
773 write = repo.ui.write
773 write = repo.ui.write
774 else:
774 else:
775 def write(s, **kw):
775 def write(s, **kw):
776 fp.write(s)
776 fp.write(s)
777
777
778
778
779 write("# HG changeset patch\n")
779 write("# HG changeset patch\n")
780 write("# User %s\n" % ctx.user())
780 write("# User %s\n" % ctx.user())
781 write("# Date %d %d\n" % ctx.date())
781 write("# Date %d %d\n" % ctx.date())
782 write("# %s\n" % util.datestr(ctx.date()))
782 write("# %s\n" % util.datestr(ctx.date()))
783 if branch and branch != 'default':
783 if branch and branch != 'default':
784 write("# Branch %s\n" % branch)
784 write("# Branch %s\n" % branch)
785 write("# Node ID %s\n" % hex(node))
785 write("# Node ID %s\n" % hex(node))
786 write("# Parent %s\n" % hex(prev))
786 write("# Parent %s\n" % hex(prev))
787 if len(parents) > 1:
787 if len(parents) > 1:
788 write("# Parent %s\n" % hex(parents[1]))
788 write("# Parent %s\n" % hex(parents[1]))
789 write(ctx.description().rstrip())
789 write(ctx.description().rstrip())
790 write("\n\n")
790 write("\n\n")
791
791
792 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
792 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
793 write(chunk, label=label)
793 write(chunk, label=label)
794
794
795 if shouldclose:
795 if shouldclose:
796 fp.close()
796 fp.close()
797
797
798 for seqno, rev in enumerate(revs):
798 for seqno, rev in enumerate(revs):
799 single(rev, seqno + 1, fp)
799 single(rev, seqno + 1, fp)
800
800
801 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
801 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
802 changes=None, stat=False, fp=None, prefix='',
802 changes=None, stat=False, fp=None, prefix='',
803 listsubrepos=False):
803 listsubrepos=False):
804 '''show diff or diffstat.'''
804 '''show diff or diffstat.'''
805 if fp is None:
805 if fp is None:
806 write = ui.write
806 write = ui.write
807 else:
807 else:
808 def write(s, **kw):
808 def write(s, **kw):
809 fp.write(s)
809 fp.write(s)
810
810
811 if stat:
811 if stat:
812 diffopts = diffopts.copy(context=0)
812 diffopts = diffopts.copy(context=0)
813 width = 80
813 width = 80
814 if not ui.plain():
814 if not ui.plain():
815 width = ui.termwidth()
815 width = ui.termwidth()
816 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
816 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
817 prefix=prefix)
817 prefix=prefix)
818 for chunk, label in patch.diffstatui(util.iterlines(chunks),
818 for chunk, label in patch.diffstatui(util.iterlines(chunks),
819 width=width,
819 width=width,
820 git=diffopts.git):
820 git=diffopts.git):
821 write(chunk, label=label)
821 write(chunk, label=label)
822 else:
822 else:
823 for chunk, label in patch.diffui(repo, node1, node2, match,
823 for chunk, label in patch.diffui(repo, node1, node2, match,
824 changes, diffopts, prefix=prefix):
824 changes, diffopts, prefix=prefix):
825 write(chunk, label=label)
825 write(chunk, label=label)
826
826
827 if listsubrepos:
827 if listsubrepos:
828 ctx1 = repo[node1]
828 ctx1 = repo[node1]
829 ctx2 = repo[node2]
829 ctx2 = repo[node2]
830 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
830 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
831 tempnode2 = node2
831 tempnode2 = node2
832 try:
832 try:
833 if node2 is not None:
833 if node2 is not None:
834 tempnode2 = ctx2.substate[subpath][1]
834 tempnode2 = ctx2.substate[subpath][1]
835 except KeyError:
835 except KeyError:
836 # A subrepo that existed in node1 was deleted between node1 and
836 # A subrepo that existed in node1 was deleted between node1 and
837 # node2 (inclusive). Thus, ctx2's substate won't contain that
837 # node2 (inclusive). Thus, ctx2's substate won't contain that
838 # subpath. The best we can do is to ignore it.
838 # subpath. The best we can do is to ignore it.
839 tempnode2 = None
839 tempnode2 = None
840 submatch = matchmod.narrowmatcher(subpath, match)
840 submatch = matchmod.narrowmatcher(subpath, match)
841 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
841 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
842 stat=stat, fp=fp, prefix=prefix)
842 stat=stat, fp=fp, prefix=prefix)
843
843
844 class changeset_printer(object):
844 class changeset_printer(object):
845 '''show changeset information when templating not requested.'''
845 '''show changeset information when templating not requested.'''
846
846
847 def __init__(self, ui, repo, matchfn, diffopts, buffered):
847 def __init__(self, ui, repo, matchfn, diffopts, buffered):
848 self.ui = ui
848 self.ui = ui
849 self.repo = repo
849 self.repo = repo
850 self.buffered = buffered
850 self.buffered = buffered
851 self.matchfn = matchfn
851 self.matchfn = matchfn
852 self.diffopts = diffopts
852 self.diffopts = diffopts
853 self.header = {}
853 self.header = {}
854 self.hunk = {}
854 self.hunk = {}
855 self.lastheader = None
855 self.lastheader = None
856 self.footer = None
856 self.footer = None
857
857
858 def flush(self, rev):
858 def flush(self, rev):
859 if rev in self.header:
859 if rev in self.header:
860 h = self.header[rev]
860 h = self.header[rev]
861 if h != self.lastheader:
861 if h != self.lastheader:
862 self.lastheader = h
862 self.lastheader = h
863 self.ui.write(h)
863 self.ui.write(h)
864 del self.header[rev]
864 del self.header[rev]
865 if rev in self.hunk:
865 if rev in self.hunk:
866 self.ui.write(self.hunk[rev])
866 self.ui.write(self.hunk[rev])
867 del self.hunk[rev]
867 del self.hunk[rev]
868 return 1
868 return 1
869 return 0
869 return 0
870
870
871 def close(self):
871 def close(self):
872 if self.footer:
872 if self.footer:
873 self.ui.write(self.footer)
873 self.ui.write(self.footer)
874
874
875 def show(self, ctx, copies=None, matchfn=None, **props):
875 def show(self, ctx, copies=None, matchfn=None, **props):
876 if self.buffered:
876 if self.buffered:
877 self.ui.pushbuffer()
877 self.ui.pushbuffer()
878 self._show(ctx, copies, matchfn, props)
878 self._show(ctx, copies, matchfn, props)
879 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
879 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
880 else:
880 else:
881 self._show(ctx, copies, matchfn, props)
881 self._show(ctx, copies, matchfn, props)
882
882
883 def _show(self, ctx, copies, matchfn, props):
883 def _show(self, ctx, copies, matchfn, props):
884 '''show a single changeset or file revision'''
884 '''show a single changeset or file revision'''
885 changenode = ctx.node()
885 changenode = ctx.node()
886 rev = ctx.rev()
886 rev = ctx.rev()
887
887
888 if self.ui.quiet:
888 if self.ui.quiet:
889 self.ui.write("%d:%s\n" % (rev, short(changenode)),
889 self.ui.write("%d:%s\n" % (rev, short(changenode)),
890 label='log.node')
890 label='log.node')
891 return
891 return
892
892
893 log = self.repo.changelog
893 log = self.repo.changelog
894 date = util.datestr(ctx.date())
894 date = util.datestr(ctx.date())
895
895
896 hexfunc = self.ui.debugflag and hex or short
896 hexfunc = self.ui.debugflag and hex or short
897
897
898 parents = [(p, hexfunc(log.node(p)))
898 parents = [(p, hexfunc(log.node(p)))
899 for p in self._meaningful_parentrevs(log, rev)]
899 for p in self._meaningful_parentrevs(log, rev)]
900
900
901 # i18n: column positioning for "hg log"
901 # i18n: column positioning for "hg log"
902 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
902 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
903 label='log.changeset changeset.%s' % ctx.phasestr())
903 label='log.changeset changeset.%s' % ctx.phasestr())
904
904
905 # branches are shown first before any other names due to backwards
905 # branches are shown first before any other names due to backwards
906 # compatibility
906 # compatibility
907 branch = ctx.branch()
907 branch = ctx.branch()
908 # don't show the default branch name
908 # don't show the default branch name
909 if branch != 'default':
909 if branch != 'default':
910 # i18n: column positioning for "hg log"
910 # i18n: column positioning for "hg log"
911 self.ui.write(_("branch: %s\n") % branch,
911 self.ui.write(_("branch: %s\n") % branch,
912 label='log.branch')
912 label='log.branch')
913
913
914 for name, ns in self.repo.names.iteritems():
914 for name, ns in self.repo.names.iteritems():
915 # branches has special logic already handled above, so here we just
915 # branches has special logic already handled above, so here we just
916 # skip it
916 # skip it
917 if name == 'branches':
917 if name == 'branches':
918 continue
918 continue
919 # we will use the templatename as the color name since those two
919 # we will use the templatename as the color name since those two
920 # should be the same
920 # should be the same
921 for name in ns.names(self.repo, changenode):
921 for name in ns.names(self.repo, changenode):
922 self.ui.write(ns.logfmt % name,
922 self.ui.write(ns.logfmt % name,
923 label='log.%s' % ns.colorname)
923 label='log.%s' % ns.colorname)
924 if self.ui.debugflag:
924 if self.ui.debugflag:
925 # i18n: column positioning for "hg log"
925 # i18n: column positioning for "hg log"
926 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
926 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
927 label='log.phase')
927 label='log.phase')
928 for parent in parents:
928 for parent in parents:
929 label = 'log.parent changeset.%s' % self.repo[parent[0]].phasestr()
929 label = 'log.parent changeset.%s' % self.repo[parent[0]].phasestr()
930 # i18n: column positioning for "hg log"
930 # i18n: column positioning for "hg log"
931 self.ui.write(_("parent: %d:%s\n") % parent,
931 self.ui.write(_("parent: %d:%s\n") % parent,
932 label=label)
932 label=label)
933
933
934 if self.ui.debugflag:
934 if self.ui.debugflag:
935 mnode = ctx.manifestnode()
935 mnode = ctx.manifestnode()
936 # i18n: column positioning for "hg log"
936 # i18n: column positioning for "hg log"
937 self.ui.write(_("manifest: %d:%s\n") %
937 self.ui.write(_("manifest: %d:%s\n") %
938 (self.repo.manifest.rev(mnode), hex(mnode)),
938 (self.repo.manifest.rev(mnode), hex(mnode)),
939 label='ui.debug log.manifest')
939 label='ui.debug log.manifest')
940 # i18n: column positioning for "hg log"
940 # i18n: column positioning for "hg log"
941 self.ui.write(_("user: %s\n") % ctx.user(),
941 self.ui.write(_("user: %s\n") % ctx.user(),
942 label='log.user')
942 label='log.user')
943 # i18n: column positioning for "hg log"
943 # i18n: column positioning for "hg log"
944 self.ui.write(_("date: %s\n") % date,
944 self.ui.write(_("date: %s\n") % date,
945 label='log.date')
945 label='log.date')
946
946
947 if self.ui.debugflag:
947 if self.ui.debugflag:
948 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
948 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
949 for key, value in zip([# i18n: column positioning for "hg log"
949 for key, value in zip([# i18n: column positioning for "hg log"
950 _("files:"),
950 _("files:"),
951 # i18n: column positioning for "hg log"
951 # i18n: column positioning for "hg log"
952 _("files+:"),
952 _("files+:"),
953 # i18n: column positioning for "hg log"
953 # i18n: column positioning for "hg log"
954 _("files-:")], files):
954 _("files-:")], files):
955 if value:
955 if value:
956 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
956 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
957 label='ui.debug log.files')
957 label='ui.debug log.files')
958 elif ctx.files() and self.ui.verbose:
958 elif ctx.files() and self.ui.verbose:
959 # i18n: column positioning for "hg log"
959 # i18n: column positioning for "hg log"
960 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
960 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
961 label='ui.note log.files')
961 label='ui.note log.files')
962 if copies and self.ui.verbose:
962 if copies and self.ui.verbose:
963 copies = ['%s (%s)' % c for c in copies]
963 copies = ['%s (%s)' % c for c in copies]
964 # i18n: column positioning for "hg log"
964 # i18n: column positioning for "hg log"
965 self.ui.write(_("copies: %s\n") % ' '.join(copies),
965 self.ui.write(_("copies: %s\n") % ' '.join(copies),
966 label='ui.note log.copies')
966 label='ui.note log.copies')
967
967
968 extra = ctx.extra()
968 extra = ctx.extra()
969 if extra and self.ui.debugflag:
969 if extra and self.ui.debugflag:
970 for key, value in sorted(extra.items()):
970 for key, value in sorted(extra.items()):
971 # i18n: column positioning for "hg log"
971 # i18n: column positioning for "hg log"
972 self.ui.write(_("extra: %s=%s\n")
972 self.ui.write(_("extra: %s=%s\n")
973 % (key, value.encode('string_escape')),
973 % (key, value.encode('string_escape')),
974 label='ui.debug log.extra')
974 label='ui.debug log.extra')
975
975
976 description = ctx.description().strip()
976 description = ctx.description().strip()
977 if description:
977 if description:
978 if self.ui.verbose:
978 if self.ui.verbose:
979 self.ui.write(_("description:\n"),
979 self.ui.write(_("description:\n"),
980 label='ui.note log.description')
980 label='ui.note log.description')
981 self.ui.write(description,
981 self.ui.write(description,
982 label='ui.note log.description')
982 label='ui.note log.description')
983 self.ui.write("\n\n")
983 self.ui.write("\n\n")
984 else:
984 else:
985 # i18n: column positioning for "hg log"
985 # i18n: column positioning for "hg log"
986 self.ui.write(_("summary: %s\n") %
986 self.ui.write(_("summary: %s\n") %
987 description.splitlines()[0],
987 description.splitlines()[0],
988 label='log.summary')
988 label='log.summary')
989 self.ui.write("\n")
989 self.ui.write("\n")
990
990
991 self.showpatch(changenode, matchfn)
991 self.showpatch(changenode, matchfn)
992
992
993 def showpatch(self, node, matchfn):
993 def showpatch(self, node, matchfn):
994 if not matchfn:
994 if not matchfn:
995 matchfn = self.matchfn
995 matchfn = self.matchfn
996 if matchfn:
996 if matchfn:
997 stat = self.diffopts.get('stat')
997 stat = self.diffopts.get('stat')
998 diff = self.diffopts.get('patch')
998 diff = self.diffopts.get('patch')
999 diffopts = patch.diffallopts(self.ui, self.diffopts)
999 diffopts = patch.diffallopts(self.ui, self.diffopts)
1000 prev = self.repo.changelog.parents(node)[0]
1000 prev = self.repo.changelog.parents(node)[0]
1001 if stat:
1001 if stat:
1002 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1002 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1003 match=matchfn, stat=True)
1003 match=matchfn, stat=True)
1004 if diff:
1004 if diff:
1005 if stat:
1005 if stat:
1006 self.ui.write("\n")
1006 self.ui.write("\n")
1007 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1007 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1008 match=matchfn, stat=False)
1008 match=matchfn, stat=False)
1009 self.ui.write("\n")
1009 self.ui.write("\n")
1010
1010
1011 def _meaningful_parentrevs(self, log, rev):
1011 def _meaningful_parentrevs(self, log, rev):
1012 """Return list of meaningful (or all if debug) parentrevs for rev.
1012 """Return list of meaningful (or all if debug) parentrevs for rev.
1013
1013
1014 For merges (two non-nullrev revisions) both parents are meaningful.
1014 For merges (two non-nullrev revisions) both parents are meaningful.
1015 Otherwise the first parent revision is considered meaningful if it
1015 Otherwise the first parent revision is considered meaningful if it
1016 is not the preceding revision.
1016 is not the preceding revision.
1017 """
1017 """
1018 parents = log.parentrevs(rev)
1018 parents = log.parentrevs(rev)
1019 if not self.ui.debugflag and parents[1] == nullrev:
1019 if not self.ui.debugflag and parents[1] == nullrev:
1020 if parents[0] >= rev - 1:
1020 if parents[0] >= rev - 1:
1021 parents = []
1021 parents = []
1022 else:
1022 else:
1023 parents = [parents[0]]
1023 parents = [parents[0]]
1024 return parents
1024 return parents
1025
1025
1026 class jsonchangeset(changeset_printer):
1026 class jsonchangeset(changeset_printer):
1027 '''format changeset information.'''
1027 '''format changeset information.'''
1028
1028
1029 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1029 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1030 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1030 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1031 self.cache = {}
1031 self.cache = {}
1032 self._first = True
1032 self._first = True
1033
1033
1034 def close(self):
1034 def close(self):
1035 if not self._first:
1035 if not self._first:
1036 self.ui.write("\n]\n")
1036 self.ui.write("\n]\n")
1037 else:
1037 else:
1038 self.ui.write("[]\n")
1038 self.ui.write("[]\n")
1039
1039
1040 def _show(self, ctx, copies, matchfn, props):
1040 def _show(self, ctx, copies, matchfn, props):
1041 '''show a single changeset or file revision'''
1041 '''show a single changeset or file revision'''
1042 hexnode = hex(ctx.node())
1042 hexnode = hex(ctx.node())
1043 rev = ctx.rev()
1043 rev = ctx.rev()
1044 j = encoding.jsonescape
1044 j = encoding.jsonescape
1045
1045
1046 if self._first:
1046 if self._first:
1047 self.ui.write("[\n {")
1047 self.ui.write("[\n {")
1048 self._first = False
1048 self._first = False
1049 else:
1049 else:
1050 self.ui.write(",\n {")
1050 self.ui.write(",\n {")
1051
1051
1052 if self.ui.quiet:
1052 if self.ui.quiet:
1053 self.ui.write('\n "rev": %d' % rev)
1053 self.ui.write('\n "rev": %d' % rev)
1054 self.ui.write(',\n "node": "%s"' % hexnode)
1054 self.ui.write(',\n "node": "%s"' % hexnode)
1055 self.ui.write('\n }')
1055 self.ui.write('\n }')
1056 return
1056 return
1057
1057
1058 self.ui.write('\n "rev": %d' % rev)
1058 self.ui.write('\n "rev": %d' % rev)
1059 self.ui.write(',\n "node": "%s"' % hexnode)
1059 self.ui.write(',\n "node": "%s"' % hexnode)
1060 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1060 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1061 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1061 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1062 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1062 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1063 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1063 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1064 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1064 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1065
1065
1066 self.ui.write(',\n "bookmarks": [%s]' %
1066 self.ui.write(',\n "bookmarks": [%s]' %
1067 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1067 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1068 self.ui.write(',\n "tags": [%s]' %
1068 self.ui.write(',\n "tags": [%s]' %
1069 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1069 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1070 self.ui.write(',\n "parents": [%s]' %
1070 self.ui.write(',\n "parents": [%s]' %
1071 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1071 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1072
1072
1073 if self.ui.debugflag:
1073 if self.ui.debugflag:
1074 self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode()))
1074 self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode()))
1075
1075
1076 self.ui.write(',\n "extra": {%s}' %
1076 self.ui.write(',\n "extra": {%s}' %
1077 ", ".join('"%s": "%s"' % (j(k), j(v))
1077 ", ".join('"%s": "%s"' % (j(k), j(v))
1078 for k, v in ctx.extra().items()))
1078 for k, v in ctx.extra().items()))
1079
1079
1080 files = ctx.p1().status(ctx)
1080 files = ctx.p1().status(ctx)
1081 self.ui.write(',\n "modified": [%s]' %
1081 self.ui.write(',\n "modified": [%s]' %
1082 ", ".join('"%s"' % j(f) for f in files[0]))
1082 ", ".join('"%s"' % j(f) for f in files[0]))
1083 self.ui.write(',\n "added": [%s]' %
1083 self.ui.write(',\n "added": [%s]' %
1084 ", ".join('"%s"' % j(f) for f in files[1]))
1084 ", ".join('"%s"' % j(f) for f in files[1]))
1085 self.ui.write(',\n "removed": [%s]' %
1085 self.ui.write(',\n "removed": [%s]' %
1086 ", ".join('"%s"' % j(f) for f in files[2]))
1086 ", ".join('"%s"' % j(f) for f in files[2]))
1087
1087
1088 elif self.ui.verbose:
1088 elif self.ui.verbose:
1089 self.ui.write(',\n "files": [%s]' %
1089 self.ui.write(',\n "files": [%s]' %
1090 ", ".join('"%s"' % j(f) for f in ctx.files()))
1090 ", ".join('"%s"' % j(f) for f in ctx.files()))
1091
1091
1092 if copies:
1092 if copies:
1093 self.ui.write(',\n "copies": {%s}' %
1093 self.ui.write(',\n "copies": {%s}' %
1094 ", ".join('"%s": "%s"' % (j(k), j(v))
1094 ", ".join('"%s": "%s"' % (j(k), j(v))
1095 for k, v in copies))
1095 for k, v in copies))
1096
1096
1097 matchfn = self.matchfn
1097 matchfn = self.matchfn
1098 if matchfn:
1098 if matchfn:
1099 stat = self.diffopts.get('stat')
1099 stat = self.diffopts.get('stat')
1100 diff = self.diffopts.get('patch')
1100 diff = self.diffopts.get('patch')
1101 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1101 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1102 node, prev = ctx.node(), ctx.p1().node()
1102 node, prev = ctx.node(), ctx.p1().node()
1103 if stat:
1103 if stat:
1104 self.ui.pushbuffer()
1104 self.ui.pushbuffer()
1105 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1105 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1106 match=matchfn, stat=True)
1106 match=matchfn, stat=True)
1107 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1107 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1108 if diff:
1108 if diff:
1109 self.ui.pushbuffer()
1109 self.ui.pushbuffer()
1110 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1110 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1111 match=matchfn, stat=False)
1111 match=matchfn, stat=False)
1112 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1112 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1113
1113
1114 self.ui.write("\n }")
1114 self.ui.write("\n }")
1115
1115
1116 class changeset_templater(changeset_printer):
1116 class changeset_templater(changeset_printer):
1117 '''format changeset information.'''
1117 '''format changeset information.'''
1118
1118
1119 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1119 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1120 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1120 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1121 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1121 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1122 defaulttempl = {
1122 defaulttempl = {
1123 'parent': '{rev}:{node|formatnode} ',
1123 'parent': '{rev}:{node|formatnode} ',
1124 'manifest': '{rev}:{node|formatnode}',
1124 'manifest': '{rev}:{node|formatnode}',
1125 'file_copy': '{name} ({source})',
1125 'file_copy': '{name} ({source})',
1126 'extra': '{key}={value|stringescape}'
1126 'extra': '{key}={value|stringescape}'
1127 }
1127 }
1128 # filecopy is preserved for compatibility reasons
1128 # filecopy is preserved for compatibility reasons
1129 defaulttempl['filecopy'] = defaulttempl['file_copy']
1129 defaulttempl['filecopy'] = defaulttempl['file_copy']
1130 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1130 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1131 cache=defaulttempl)
1131 cache=defaulttempl)
1132 if tmpl:
1132 if tmpl:
1133 self.t.cache['changeset'] = tmpl
1133 self.t.cache['changeset'] = tmpl
1134
1134
1135 self.cache = {}
1135 self.cache = {}
1136
1136
1137 def _meaningful_parentrevs(self, ctx):
1137 def _meaningful_parentrevs(self, ctx):
1138 """Return list of meaningful (or all if debug) parentrevs for rev.
1138 """Return list of meaningful (or all if debug) parentrevs for rev.
1139 """
1139 """
1140 parents = ctx.parents()
1140 parents = ctx.parents()
1141 if len(parents) > 1:
1141 if len(parents) > 1:
1142 return parents
1142 return parents
1143 if self.ui.debugflag:
1143 if self.ui.debugflag:
1144 return [parents[0], self.repo['null']]
1144 return [parents[0], self.repo['null']]
1145 if parents[0].rev() >= ctx.rev() - 1:
1145 if parents[0].rev() >= ctx.rev() - 1:
1146 return []
1146 return []
1147 return parents
1147 return parents
1148
1148
1149 def _show(self, ctx, copies, matchfn, props):
1149 def _show(self, ctx, copies, matchfn, props):
1150 '''show a single changeset or file revision'''
1150 '''show a single changeset or file revision'''
1151
1151
1152 showlist = templatekw.showlist
1152 showlist = templatekw.showlist
1153
1153
1154 # showparents() behaviour depends on ui trace level which
1154 # showparents() behaviour depends on ui trace level which
1155 # causes unexpected behaviours at templating level and makes
1155 # causes unexpected behaviours at templating level and makes
1156 # it harder to extract it in a standalone function. Its
1156 # it harder to extract it in a standalone function. Its
1157 # behaviour cannot be changed so leave it here for now.
1157 # behaviour cannot be changed so leave it here for now.
1158 def showparents(**args):
1158 def showparents(**args):
1159 ctx = args['ctx']
1159 ctx = args['ctx']
1160 parents = [[('rev', p.rev()),
1160 parents = [[('rev', p.rev()),
1161 ('node', p.hex()),
1161 ('node', p.hex()),
1162 ('phase', p.phasestr())]
1162 ('phase', p.phasestr())]
1163 for p in self._meaningful_parentrevs(ctx)]
1163 for p in self._meaningful_parentrevs(ctx)]
1164 return showlist('parent', parents, **args)
1164 return showlist('parent', parents, **args)
1165
1165
1166 props = props.copy()
1166 props = props.copy()
1167 props.update(templatekw.keywords)
1167 props.update(templatekw.keywords)
1168 props['parents'] = showparents
1168 props['parents'] = showparents
1169 props['templ'] = self.t
1169 props['templ'] = self.t
1170 props['ctx'] = ctx
1170 props['ctx'] = ctx
1171 props['repo'] = self.repo
1171 props['repo'] = self.repo
1172 props['revcache'] = {'copies': copies}
1172 props['revcache'] = {'copies': copies}
1173 props['cache'] = self.cache
1173 props['cache'] = self.cache
1174
1174
1175 # find correct templates for current mode
1175 # find correct templates for current mode
1176
1176
1177 tmplmodes = [
1177 tmplmodes = [
1178 (True, None),
1178 (True, None),
1179 (self.ui.verbose, 'verbose'),
1179 (self.ui.verbose, 'verbose'),
1180 (self.ui.quiet, 'quiet'),
1180 (self.ui.quiet, 'quiet'),
1181 (self.ui.debugflag, 'debug'),
1181 (self.ui.debugflag, 'debug'),
1182 ]
1182 ]
1183
1183
1184 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1184 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1185 for mode, postfix in tmplmodes:
1185 for mode, postfix in tmplmodes:
1186 for type in types:
1186 for type in types:
1187 cur = postfix and ('%s_%s' % (type, postfix)) or type
1187 cur = postfix and ('%s_%s' % (type, postfix)) or type
1188 if mode and cur in self.t:
1188 if mode and cur in self.t:
1189 types[type] = cur
1189 types[type] = cur
1190
1190
1191 try:
1191 try:
1192
1192
1193 # write header
1193 # write header
1194 if types['header']:
1194 if types['header']:
1195 h = templater.stringify(self.t(types['header'], **props))
1195 h = templater.stringify(self.t(types['header'], **props))
1196 if self.buffered:
1196 if self.buffered:
1197 self.header[ctx.rev()] = h
1197 self.header[ctx.rev()] = h
1198 else:
1198 else:
1199 if self.lastheader != h:
1199 if self.lastheader != h:
1200 self.lastheader = h
1200 self.lastheader = h
1201 self.ui.write(h)
1201 self.ui.write(h)
1202
1202
1203 # write changeset metadata, then patch if requested
1203 # write changeset metadata, then patch if requested
1204 key = types['changeset']
1204 key = types['changeset']
1205 self.ui.write(templater.stringify(self.t(key, **props)))
1205 self.ui.write(templater.stringify(self.t(key, **props)))
1206 self.showpatch(ctx.node(), matchfn)
1206 self.showpatch(ctx.node(), matchfn)
1207
1207
1208 if types['footer']:
1208 if types['footer']:
1209 if not self.footer:
1209 if not self.footer:
1210 self.footer = templater.stringify(self.t(types['footer'],
1210 self.footer = templater.stringify(self.t(types['footer'],
1211 **props))
1211 **props))
1212
1212
1213 except KeyError, inst:
1213 except KeyError, inst:
1214 msg = _("%s: no key named '%s'")
1214 msg = _("%s: no key named '%s'")
1215 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1215 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1216 except SyntaxError, inst:
1216 except SyntaxError, inst:
1217 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1217 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1218
1218
1219 def gettemplate(ui, tmpl, style):
1219 def gettemplate(ui, tmpl, style):
1220 """
1220 """
1221 Find the template matching the given template spec or style.
1221 Find the template matching the given template spec or style.
1222 """
1222 """
1223
1223
1224 # ui settings
1224 # ui settings
1225 if not tmpl and not style: # template are stronger than style
1225 if not tmpl and not style: # template are stronger than style
1226 tmpl = ui.config('ui', 'logtemplate')
1226 tmpl = ui.config('ui', 'logtemplate')
1227 if tmpl:
1227 if tmpl:
1228 try:
1228 try:
1229 tmpl = templater.parsestring(tmpl)
1229 tmpl = templater.parsestring(tmpl)
1230 except SyntaxError:
1230 except SyntaxError:
1231 tmpl = templater.parsestring(tmpl, quoted=False)
1231 tmpl = templater.parsestring(tmpl, quoted=False)
1232 return tmpl, None
1232 return tmpl, None
1233 else:
1233 else:
1234 style = util.expandpath(ui.config('ui', 'style', ''))
1234 style = util.expandpath(ui.config('ui', 'style', ''))
1235
1235
1236 if not tmpl and style:
1236 if not tmpl and style:
1237 mapfile = style
1237 mapfile = style
1238 if not os.path.split(mapfile)[0]:
1238 if not os.path.split(mapfile)[0]:
1239 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1239 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1240 or templater.templatepath(mapfile))
1240 or templater.templatepath(mapfile))
1241 if mapname:
1241 if mapname:
1242 mapfile = mapname
1242 mapfile = mapname
1243 return None, mapfile
1243 return None, mapfile
1244
1244
1245 if not tmpl:
1245 if not tmpl:
1246 return None, None
1246 return None, None
1247
1247
1248 # looks like a literal template?
1248 # looks like a literal template?
1249 if '{' in tmpl:
1249 if '{' in tmpl:
1250 return tmpl, None
1250 return tmpl, None
1251
1251
1252 # perhaps a stock style?
1252 # perhaps a stock style?
1253 if not os.path.split(tmpl)[0]:
1253 if not os.path.split(tmpl)[0]:
1254 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1254 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1255 or templater.templatepath(tmpl))
1255 or templater.templatepath(tmpl))
1256 if mapname and os.path.isfile(mapname):
1256 if mapname and os.path.isfile(mapname):
1257 return None, mapname
1257 return None, mapname
1258
1258
1259 # perhaps it's a reference to [templates]
1259 # perhaps it's a reference to [templates]
1260 t = ui.config('templates', tmpl)
1260 t = ui.config('templates', tmpl)
1261 if t:
1261 if t:
1262 try:
1262 try:
1263 tmpl = templater.parsestring(t)
1263 tmpl = templater.parsestring(t)
1264 except SyntaxError:
1264 except SyntaxError:
1265 tmpl = templater.parsestring(t, quoted=False)
1265 tmpl = templater.parsestring(t, quoted=False)
1266 return tmpl, None
1266 return tmpl, None
1267
1267
1268 if tmpl == 'list':
1268 if tmpl == 'list':
1269 ui.write(_("available styles: %s\n") % templater.stylelist())
1269 ui.write(_("available styles: %s\n") % templater.stylelist())
1270 raise util.Abort(_("specify a template"))
1270 raise util.Abort(_("specify a template"))
1271
1271
1272 # perhaps it's a path to a map or a template
1272 # perhaps it's a path to a map or a template
1273 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1273 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1274 # is it a mapfile for a style?
1274 # is it a mapfile for a style?
1275 if os.path.basename(tmpl).startswith("map-"):
1275 if os.path.basename(tmpl).startswith("map-"):
1276 return None, os.path.realpath(tmpl)
1276 return None, os.path.realpath(tmpl)
1277 tmpl = open(tmpl).read()
1277 tmpl = open(tmpl).read()
1278 return tmpl, None
1278 return tmpl, None
1279
1279
1280 # constant string?
1280 # constant string?
1281 return tmpl, None
1281 return tmpl, None
1282
1282
1283 def show_changeset(ui, repo, opts, buffered=False):
1283 def show_changeset(ui, repo, opts, buffered=False):
1284 """show one changeset using template or regular display.
1284 """show one changeset using template or regular display.
1285
1285
1286 Display format will be the first non-empty hit of:
1286 Display format will be the first non-empty hit of:
1287 1. option 'template'
1287 1. option 'template'
1288 2. option 'style'
1288 2. option 'style'
1289 3. [ui] setting 'logtemplate'
1289 3. [ui] setting 'logtemplate'
1290 4. [ui] setting 'style'
1290 4. [ui] setting 'style'
1291 If all of these values are either the unset or the empty string,
1291 If all of these values are either the unset or the empty string,
1292 regular display via changeset_printer() is done.
1292 regular display via changeset_printer() is done.
1293 """
1293 """
1294 # options
1294 # options
1295 matchfn = None
1295 matchfn = None
1296 if opts.get('patch') or opts.get('stat'):
1296 if opts.get('patch') or opts.get('stat'):
1297 matchfn = scmutil.matchall(repo)
1297 matchfn = scmutil.matchall(repo)
1298
1298
1299 if opts.get('template') == 'json':
1299 if opts.get('template') == 'json':
1300 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1300 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1301
1301
1302 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1302 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1303
1303
1304 if not tmpl and not mapfile:
1304 if not tmpl and not mapfile:
1305 return changeset_printer(ui, repo, matchfn, opts, buffered)
1305 return changeset_printer(ui, repo, matchfn, opts, buffered)
1306
1306
1307 try:
1307 try:
1308 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1308 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1309 buffered)
1309 buffered)
1310 except SyntaxError, inst:
1310 except SyntaxError, inst:
1311 raise util.Abort(inst.args[0])
1311 raise util.Abort(inst.args[0])
1312 return t
1312 return t
1313
1313
1314 def showmarker(ui, marker):
1314 def showmarker(ui, marker):
1315 """utility function to display obsolescence marker in a readable way
1315 """utility function to display obsolescence marker in a readable way
1316
1316
1317 To be used by debug function."""
1317 To be used by debug function."""
1318 ui.write(hex(marker.precnode()))
1318 ui.write(hex(marker.precnode()))
1319 for repl in marker.succnodes():
1319 for repl in marker.succnodes():
1320 ui.write(' ')
1320 ui.write(' ')
1321 ui.write(hex(repl))
1321 ui.write(hex(repl))
1322 ui.write(' %X ' % marker.flags())
1322 ui.write(' %X ' % marker.flags())
1323 parents = marker.parentnodes()
1323 parents = marker.parentnodes()
1324 if parents is not None:
1324 if parents is not None:
1325 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1325 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1326 ui.write('(%s) ' % util.datestr(marker.date()))
1326 ui.write('(%s) ' % util.datestr(marker.date()))
1327 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1327 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1328 sorted(marker.metadata().items())
1328 sorted(marker.metadata().items())
1329 if t[0] != 'date')))
1329 if t[0] != 'date')))
1330 ui.write('\n')
1330 ui.write('\n')
1331
1331
1332 def finddate(ui, repo, date):
1332 def finddate(ui, repo, date):
1333 """Find the tipmost changeset that matches the given date spec"""
1333 """Find the tipmost changeset that matches the given date spec"""
1334
1334
1335 df = util.matchdate(date)
1335 df = util.matchdate(date)
1336 m = scmutil.matchall(repo)
1336 m = scmutil.matchall(repo)
1337 results = {}
1337 results = {}
1338
1338
1339 def prep(ctx, fns):
1339 def prep(ctx, fns):
1340 d = ctx.date()
1340 d = ctx.date()
1341 if df(d[0]):
1341 if df(d[0]):
1342 results[ctx.rev()] = d
1342 results[ctx.rev()] = d
1343
1343
1344 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1344 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1345 rev = ctx.rev()
1345 rev = ctx.rev()
1346 if rev in results:
1346 if rev in results:
1347 ui.status(_("found revision %s from %s\n") %
1347 ui.status(_("found revision %s from %s\n") %
1348 (rev, util.datestr(results[rev])))
1348 (rev, util.datestr(results[rev])))
1349 return str(rev)
1349 return str(rev)
1350
1350
1351 raise util.Abort(_("revision matching date not found"))
1351 raise util.Abort(_("revision matching date not found"))
1352
1352
1353 def increasingwindows(windowsize=8, sizelimit=512):
1353 def increasingwindows(windowsize=8, sizelimit=512):
1354 while True:
1354 while True:
1355 yield windowsize
1355 yield windowsize
1356 if windowsize < sizelimit:
1356 if windowsize < sizelimit:
1357 windowsize *= 2
1357 windowsize *= 2
1358
1358
1359 class FileWalkError(Exception):
1359 class FileWalkError(Exception):
1360 pass
1360 pass
1361
1361
1362 def walkfilerevs(repo, match, follow, revs, fncache):
1362 def walkfilerevs(repo, match, follow, revs, fncache):
1363 '''Walks the file history for the matched files.
1363 '''Walks the file history for the matched files.
1364
1364
1365 Returns the changeset revs that are involved in the file history.
1365 Returns the changeset revs that are involved in the file history.
1366
1366
1367 Throws FileWalkError if the file history can't be walked using
1367 Throws FileWalkError if the file history can't be walked using
1368 filelogs alone.
1368 filelogs alone.
1369 '''
1369 '''
1370 wanted = set()
1370 wanted = set()
1371 copies = []
1371 copies = []
1372 minrev, maxrev = min(revs), max(revs)
1372 minrev, maxrev = min(revs), max(revs)
1373 def filerevgen(filelog, last):
1373 def filerevgen(filelog, last):
1374 """
1374 """
1375 Only files, no patterns. Check the history of each file.
1375 Only files, no patterns. Check the history of each file.
1376
1376
1377 Examines filelog entries within minrev, maxrev linkrev range
1377 Examines filelog entries within minrev, maxrev linkrev range
1378 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1378 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1379 tuples in backwards order
1379 tuples in backwards order
1380 """
1380 """
1381 cl_count = len(repo)
1381 cl_count = len(repo)
1382 revs = []
1382 revs = []
1383 for j in xrange(0, last + 1):
1383 for j in xrange(0, last + 1):
1384 linkrev = filelog.linkrev(j)
1384 linkrev = filelog.linkrev(j)
1385 if linkrev < minrev:
1385 if linkrev < minrev:
1386 continue
1386 continue
1387 # only yield rev for which we have the changelog, it can
1387 # only yield rev for which we have the changelog, it can
1388 # happen while doing "hg log" during a pull or commit
1388 # happen while doing "hg log" during a pull or commit
1389 if linkrev >= cl_count:
1389 if linkrev >= cl_count:
1390 break
1390 break
1391
1391
1392 parentlinkrevs = []
1392 parentlinkrevs = []
1393 for p in filelog.parentrevs(j):
1393 for p in filelog.parentrevs(j):
1394 if p != nullrev:
1394 if p != nullrev:
1395 parentlinkrevs.append(filelog.linkrev(p))
1395 parentlinkrevs.append(filelog.linkrev(p))
1396 n = filelog.node(j)
1396 n = filelog.node(j)
1397 revs.append((linkrev, parentlinkrevs,
1397 revs.append((linkrev, parentlinkrevs,
1398 follow and filelog.renamed(n)))
1398 follow and filelog.renamed(n)))
1399
1399
1400 return reversed(revs)
1400 return reversed(revs)
1401 def iterfiles():
1401 def iterfiles():
1402 pctx = repo['.']
1402 pctx = repo['.']
1403 for filename in match.files():
1403 for filename in match.files():
1404 if follow:
1404 if follow:
1405 if filename not in pctx:
1405 if filename not in pctx:
1406 raise util.Abort(_('cannot follow file not in parent '
1406 raise util.Abort(_('cannot follow file not in parent '
1407 'revision: "%s"') % filename)
1407 'revision: "%s"') % filename)
1408 yield filename, pctx[filename].filenode()
1408 yield filename, pctx[filename].filenode()
1409 else:
1409 else:
1410 yield filename, None
1410 yield filename, None
1411 for filename_node in copies:
1411 for filename_node in copies:
1412 yield filename_node
1412 yield filename_node
1413
1413
1414 for file_, node in iterfiles():
1414 for file_, node in iterfiles():
1415 filelog = repo.file(file_)
1415 filelog = repo.file(file_)
1416 if not len(filelog):
1416 if not len(filelog):
1417 if node is None:
1417 if node is None:
1418 # A zero count may be a directory or deleted file, so
1418 # A zero count may be a directory or deleted file, so
1419 # try to find matching entries on the slow path.
1419 # try to find matching entries on the slow path.
1420 if follow:
1420 if follow:
1421 raise util.Abort(
1421 raise util.Abort(
1422 _('cannot follow nonexistent file: "%s"') % file_)
1422 _('cannot follow nonexistent file: "%s"') % file_)
1423 raise FileWalkError("Cannot walk via filelog")
1423 raise FileWalkError("Cannot walk via filelog")
1424 else:
1424 else:
1425 continue
1425 continue
1426
1426
1427 if node is None:
1427 if node is None:
1428 last = len(filelog) - 1
1428 last = len(filelog) - 1
1429 else:
1429 else:
1430 last = filelog.rev(node)
1430 last = filelog.rev(node)
1431
1431
1432
1432
1433 # keep track of all ancestors of the file
1433 # keep track of all ancestors of the file
1434 ancestors = set([filelog.linkrev(last)])
1434 ancestors = set([filelog.linkrev(last)])
1435
1435
1436 # iterate from latest to oldest revision
1436 # iterate from latest to oldest revision
1437 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1437 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1438 if not follow:
1438 if not follow:
1439 if rev > maxrev:
1439 if rev > maxrev:
1440 continue
1440 continue
1441 else:
1441 else:
1442 # Note that last might not be the first interesting
1442 # Note that last might not be the first interesting
1443 # rev to us:
1443 # rev to us:
1444 # if the file has been changed after maxrev, we'll
1444 # if the file has been changed after maxrev, we'll
1445 # have linkrev(last) > maxrev, and we still need
1445 # have linkrev(last) > maxrev, and we still need
1446 # to explore the file graph
1446 # to explore the file graph
1447 if rev not in ancestors:
1447 if rev not in ancestors:
1448 continue
1448 continue
1449 # XXX insert 1327 fix here
1449 # XXX insert 1327 fix here
1450 if flparentlinkrevs:
1450 if flparentlinkrevs:
1451 ancestors.update(flparentlinkrevs)
1451 ancestors.update(flparentlinkrevs)
1452
1452
1453 fncache.setdefault(rev, []).append(file_)
1453 fncache.setdefault(rev, []).append(file_)
1454 wanted.add(rev)
1454 wanted.add(rev)
1455 if copied:
1455 if copied:
1456 copies.append(copied)
1456 copies.append(copied)
1457
1457
1458 return wanted
1458 return wanted
1459
1459
1460 def walkchangerevs(repo, match, opts, prepare):
1460 def walkchangerevs(repo, match, opts, prepare):
1461 '''Iterate over files and the revs in which they changed.
1461 '''Iterate over files and the revs in which they changed.
1462
1462
1463 Callers most commonly need to iterate backwards over the history
1463 Callers most commonly need to iterate backwards over the history
1464 in which they are interested. Doing so has awful (quadratic-looking)
1464 in which they are interested. Doing so has awful (quadratic-looking)
1465 performance, so we use iterators in a "windowed" way.
1465 performance, so we use iterators in a "windowed" way.
1466
1466
1467 We walk a window of revisions in the desired order. Within the
1467 We walk a window of revisions in the desired order. Within the
1468 window, we first walk forwards to gather data, then in the desired
1468 window, we first walk forwards to gather data, then in the desired
1469 order (usually backwards) to display it.
1469 order (usually backwards) to display it.
1470
1470
1471 This function returns an iterator yielding contexts. Before
1471 This function returns an iterator yielding contexts. Before
1472 yielding each context, the iterator will first call the prepare
1472 yielding each context, the iterator will first call the prepare
1473 function on each context in the window in forward order.'''
1473 function on each context in the window in forward order.'''
1474
1474
1475 follow = opts.get('follow') or opts.get('follow_first')
1475 follow = opts.get('follow') or opts.get('follow_first')
1476 revs = _logrevs(repo, opts)
1476 revs = _logrevs(repo, opts)
1477 if not revs:
1477 if not revs:
1478 return []
1478 return []
1479 wanted = set()
1479 wanted = set()
1480 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1480 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1481 fncache = {}
1481 fncache = {}
1482 change = repo.changectx
1482 change = repo.changectx
1483
1483
1484 # First step is to fill wanted, the set of revisions that we want to yield.
1484 # First step is to fill wanted, the set of revisions that we want to yield.
1485 # When it does not induce extra cost, we also fill fncache for revisions in
1485 # When it does not induce extra cost, we also fill fncache for revisions in
1486 # wanted: a cache of filenames that were changed (ctx.files()) and that
1486 # wanted: a cache of filenames that were changed (ctx.files()) and that
1487 # match the file filtering conditions.
1487 # match the file filtering conditions.
1488
1488
1489 if not slowpath and not match.files():
1489 if not slowpath and not match.files():
1490 # No files, no patterns. Display all revs.
1490 # No files, no patterns. Display all revs.
1491 wanted = revs
1491 wanted = revs
1492
1492
1493 if not slowpath and match.files():
1493 if not slowpath and match.files():
1494 # We only have to read through the filelog to find wanted revisions
1494 # We only have to read through the filelog to find wanted revisions
1495
1495
1496 try:
1496 try:
1497 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1497 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1498 except FileWalkError:
1498 except FileWalkError:
1499 slowpath = True
1499 slowpath = True
1500
1500
1501 # We decided to fall back to the slowpath because at least one
1501 # We decided to fall back to the slowpath because at least one
1502 # of the paths was not a file. Check to see if at least one of them
1502 # of the paths was not a file. Check to see if at least one of them
1503 # existed in history, otherwise simply return
1503 # existed in history, otherwise simply return
1504 for path in match.files():
1504 for path in match.files():
1505 if path == '.' or path in repo.store:
1505 if path == '.' or path in repo.store:
1506 break
1506 break
1507 else:
1507 else:
1508 return []
1508 return []
1509
1509
1510 if slowpath:
1510 if slowpath:
1511 # We have to read the changelog to match filenames against
1511 # We have to read the changelog to match filenames against
1512 # changed files
1512 # changed files
1513
1513
1514 if follow:
1514 if follow:
1515 raise util.Abort(_('can only follow copies/renames for explicit '
1515 raise util.Abort(_('can only follow copies/renames for explicit '
1516 'filenames'))
1516 'filenames'))
1517
1517
1518 # The slow path checks files modified in every changeset.
1518 # The slow path checks files modified in every changeset.
1519 # This is really slow on large repos, so compute the set lazily.
1519 # This is really slow on large repos, so compute the set lazily.
1520 class lazywantedset(object):
1520 class lazywantedset(object):
1521 def __init__(self):
1521 def __init__(self):
1522 self.set = set()
1522 self.set = set()
1523 self.revs = set(revs)
1523 self.revs = set(revs)
1524
1524
1525 # No need to worry about locality here because it will be accessed
1525 # No need to worry about locality here because it will be accessed
1526 # in the same order as the increasing window below.
1526 # in the same order as the increasing window below.
1527 def __contains__(self, value):
1527 def __contains__(self, value):
1528 if value in self.set:
1528 if value in self.set:
1529 return True
1529 return True
1530 elif not value in self.revs:
1530 elif not value in self.revs:
1531 return False
1531 return False
1532 else:
1532 else:
1533 self.revs.discard(value)
1533 self.revs.discard(value)
1534 ctx = change(value)
1534 ctx = change(value)
1535 matches = filter(match, ctx.files())
1535 matches = filter(match, ctx.files())
1536 if matches:
1536 if matches:
1537 fncache[value] = matches
1537 fncache[value] = matches
1538 self.set.add(value)
1538 self.set.add(value)
1539 return True
1539 return True
1540 return False
1540 return False
1541
1541
1542 def discard(self, value):
1542 def discard(self, value):
1543 self.revs.discard(value)
1543 self.revs.discard(value)
1544 self.set.discard(value)
1544 self.set.discard(value)
1545
1545
1546 wanted = lazywantedset()
1546 wanted = lazywantedset()
1547
1547
1548 class followfilter(object):
1548 class followfilter(object):
1549 def __init__(self, onlyfirst=False):
1549 def __init__(self, onlyfirst=False):
1550 self.startrev = nullrev
1550 self.startrev = nullrev
1551 self.roots = set()
1551 self.roots = set()
1552 self.onlyfirst = onlyfirst
1552 self.onlyfirst = onlyfirst
1553
1553
1554 def match(self, rev):
1554 def match(self, rev):
1555 def realparents(rev):
1555 def realparents(rev):
1556 if self.onlyfirst:
1556 if self.onlyfirst:
1557 return repo.changelog.parentrevs(rev)[0:1]
1557 return repo.changelog.parentrevs(rev)[0:1]
1558 else:
1558 else:
1559 return filter(lambda x: x != nullrev,
1559 return filter(lambda x: x != nullrev,
1560 repo.changelog.parentrevs(rev))
1560 repo.changelog.parentrevs(rev))
1561
1561
1562 if self.startrev == nullrev:
1562 if self.startrev == nullrev:
1563 self.startrev = rev
1563 self.startrev = rev
1564 return True
1564 return True
1565
1565
1566 if rev > self.startrev:
1566 if rev > self.startrev:
1567 # forward: all descendants
1567 # forward: all descendants
1568 if not self.roots:
1568 if not self.roots:
1569 self.roots.add(self.startrev)
1569 self.roots.add(self.startrev)
1570 for parent in realparents(rev):
1570 for parent in realparents(rev):
1571 if parent in self.roots:
1571 if parent in self.roots:
1572 self.roots.add(rev)
1572 self.roots.add(rev)
1573 return True
1573 return True
1574 else:
1574 else:
1575 # backwards: all parents
1575 # backwards: all parents
1576 if not self.roots:
1576 if not self.roots:
1577 self.roots.update(realparents(self.startrev))
1577 self.roots.update(realparents(self.startrev))
1578 if rev in self.roots:
1578 if rev in self.roots:
1579 self.roots.remove(rev)
1579 self.roots.remove(rev)
1580 self.roots.update(realparents(rev))
1580 self.roots.update(realparents(rev))
1581 return True
1581 return True
1582
1582
1583 return False
1583 return False
1584
1584
1585 # it might be worthwhile to do this in the iterator if the rev range
1585 # it might be worthwhile to do this in the iterator if the rev range
1586 # is descending and the prune args are all within that range
1586 # is descending and the prune args are all within that range
1587 for rev in opts.get('prune', ()):
1587 for rev in opts.get('prune', ()):
1588 rev = repo[rev].rev()
1588 rev = repo[rev].rev()
1589 ff = followfilter()
1589 ff = followfilter()
1590 stop = min(revs[0], revs[-1])
1590 stop = min(revs[0], revs[-1])
1591 for x in xrange(rev, stop - 1, -1):
1591 for x in xrange(rev, stop - 1, -1):
1592 if ff.match(x):
1592 if ff.match(x):
1593 wanted = wanted - [x]
1593 wanted = wanted - [x]
1594
1594
1595 # Now that wanted is correctly initialized, we can iterate over the
1595 # Now that wanted is correctly initialized, we can iterate over the
1596 # revision range, yielding only revisions in wanted.
1596 # revision range, yielding only revisions in wanted.
1597 def iterate():
1597 def iterate():
1598 if follow and not match.files():
1598 if follow and not match.files():
1599 ff = followfilter(onlyfirst=opts.get('follow_first'))
1599 ff = followfilter(onlyfirst=opts.get('follow_first'))
1600 def want(rev):
1600 def want(rev):
1601 return ff.match(rev) and rev in wanted
1601 return ff.match(rev) and rev in wanted
1602 else:
1602 else:
1603 def want(rev):
1603 def want(rev):
1604 return rev in wanted
1604 return rev in wanted
1605
1605
1606 it = iter(revs)
1606 it = iter(revs)
1607 stopiteration = False
1607 stopiteration = False
1608 for windowsize in increasingwindows():
1608 for windowsize in increasingwindows():
1609 nrevs = []
1609 nrevs = []
1610 for i in xrange(windowsize):
1610 for i in xrange(windowsize):
1611 try:
1611 try:
1612 rev = it.next()
1612 rev = it.next()
1613 if want(rev):
1613 if want(rev):
1614 nrevs.append(rev)
1614 nrevs.append(rev)
1615 except (StopIteration):
1615 except (StopIteration):
1616 stopiteration = True
1616 stopiteration = True
1617 break
1617 break
1618 for rev in sorted(nrevs):
1618 for rev in sorted(nrevs):
1619 fns = fncache.get(rev)
1619 fns = fncache.get(rev)
1620 ctx = change(rev)
1620 ctx = change(rev)
1621 if not fns:
1621 if not fns:
1622 def fns_generator():
1622 def fns_generator():
1623 for f in ctx.files():
1623 for f in ctx.files():
1624 if match(f):
1624 if match(f):
1625 yield f
1625 yield f
1626 fns = fns_generator()
1626 fns = fns_generator()
1627 prepare(ctx, fns)
1627 prepare(ctx, fns)
1628 for rev in nrevs:
1628 for rev in nrevs:
1629 yield change(rev)
1629 yield change(rev)
1630
1630
1631 if stopiteration:
1631 if stopiteration:
1632 break
1632 break
1633
1633
1634 return iterate()
1634 return iterate()
1635
1635
1636 def _makefollowlogfilematcher(repo, files, followfirst):
1636 def _makefollowlogfilematcher(repo, files, followfirst):
1637 # When displaying a revision with --patch --follow FILE, we have
1637 # When displaying a revision with --patch --follow FILE, we have
1638 # to know which file of the revision must be diffed. With
1638 # to know which file of the revision must be diffed. With
1639 # --follow, we want the names of the ancestors of FILE in the
1639 # --follow, we want the names of the ancestors of FILE in the
1640 # revision, stored in "fcache". "fcache" is populated by
1640 # revision, stored in "fcache". "fcache" is populated by
1641 # reproducing the graph traversal already done by --follow revset
1641 # reproducing the graph traversal already done by --follow revset
1642 # and relating linkrevs to file names (which is not "correct" but
1642 # and relating linkrevs to file names (which is not "correct" but
1643 # good enough).
1643 # good enough).
1644 fcache = {}
1644 fcache = {}
1645 fcacheready = [False]
1645 fcacheready = [False]
1646 pctx = repo['.']
1646 pctx = repo['.']
1647
1647
1648 def populate():
1648 def populate():
1649 for fn in files:
1649 for fn in files:
1650 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1650 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1651 for c in i:
1651 for c in i:
1652 fcache.setdefault(c.linkrev(), set()).add(c.path())
1652 fcache.setdefault(c.linkrev(), set()).add(c.path())
1653
1653
1654 def filematcher(rev):
1654 def filematcher(rev):
1655 if not fcacheready[0]:
1655 if not fcacheready[0]:
1656 # Lazy initialization
1656 # Lazy initialization
1657 fcacheready[0] = True
1657 fcacheready[0] = True
1658 populate()
1658 populate()
1659 return scmutil.matchfiles(repo, fcache.get(rev, []))
1659 return scmutil.matchfiles(repo, fcache.get(rev, []))
1660
1660
1661 return filematcher
1661 return filematcher
1662
1662
1663 def _makenofollowlogfilematcher(repo, pats, opts):
1663 def _makenofollowlogfilematcher(repo, pats, opts):
1664 '''hook for extensions to override the filematcher for non-follow cases'''
1664 '''hook for extensions to override the filematcher for non-follow cases'''
1665 return None
1665 return None
1666
1666
1667 def _makelogrevset(repo, pats, opts, revs):
1667 def _makelogrevset(repo, pats, opts, revs):
1668 """Return (expr, filematcher) where expr is a revset string built
1668 """Return (expr, filematcher) where expr is a revset string built
1669 from log options and file patterns or None. If --stat or --patch
1669 from log options and file patterns or None. If --stat or --patch
1670 are not passed filematcher is None. Otherwise it is a callable
1670 are not passed filematcher is None. Otherwise it is a callable
1671 taking a revision number and returning a match objects filtering
1671 taking a revision number and returning a match objects filtering
1672 the files to be detailed when displaying the revision.
1672 the files to be detailed when displaying the revision.
1673 """
1673 """
1674 opt2revset = {
1674 opt2revset = {
1675 'no_merges': ('not merge()', None),
1675 'no_merges': ('not merge()', None),
1676 'only_merges': ('merge()', None),
1676 'only_merges': ('merge()', None),
1677 '_ancestors': ('ancestors(%(val)s)', None),
1677 '_ancestors': ('ancestors(%(val)s)', None),
1678 '_fancestors': ('_firstancestors(%(val)s)', None),
1678 '_fancestors': ('_firstancestors(%(val)s)', None),
1679 '_descendants': ('descendants(%(val)s)', None),
1679 '_descendants': ('descendants(%(val)s)', None),
1680 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1680 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1681 '_matchfiles': ('_matchfiles(%(val)s)', None),
1681 '_matchfiles': ('_matchfiles(%(val)s)', None),
1682 'date': ('date(%(val)r)', None),
1682 'date': ('date(%(val)r)', None),
1683 'branch': ('branch(%(val)r)', ' or '),
1683 'branch': ('branch(%(val)r)', ' or '),
1684 '_patslog': ('filelog(%(val)r)', ' or '),
1684 '_patslog': ('filelog(%(val)r)', ' or '),
1685 '_patsfollow': ('follow(%(val)r)', ' or '),
1685 '_patsfollow': ('follow(%(val)r)', ' or '),
1686 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1686 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1687 'keyword': ('keyword(%(val)r)', ' or '),
1687 'keyword': ('keyword(%(val)r)', ' or '),
1688 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1688 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1689 'user': ('user(%(val)r)', ' or '),
1689 'user': ('user(%(val)r)', ' or '),
1690 }
1690 }
1691
1691
1692 opts = dict(opts)
1692 opts = dict(opts)
1693 # follow or not follow?
1693 # follow or not follow?
1694 follow = opts.get('follow') or opts.get('follow_first')
1694 follow = opts.get('follow') or opts.get('follow_first')
1695 followfirst = opts.get('follow_first') and 1 or 0
1695 followfirst = opts.get('follow_first') and 1 or 0
1696 # --follow with FILE behaviour depends on revs...
1696 # --follow with FILE behaviour depends on revs...
1697 it = iter(revs)
1697 it = iter(revs)
1698 startrev = it.next()
1698 startrev = it.next()
1699 try:
1699 try:
1700 followdescendants = startrev < it.next()
1700 followdescendants = startrev < it.next()
1701 except (StopIteration):
1701 except (StopIteration):
1702 followdescendants = False
1702 followdescendants = False
1703
1703
1704 # branch and only_branch are really aliases and must be handled at
1704 # branch and only_branch are really aliases and must be handled at
1705 # the same time
1705 # the same time
1706 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1706 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1707 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1707 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1708 # pats/include/exclude are passed to match.match() directly in
1708 # pats/include/exclude are passed to match.match() directly in
1709 # _matchfiles() revset but walkchangerevs() builds its matcher with
1709 # _matchfiles() revset but walkchangerevs() builds its matcher with
1710 # scmutil.match(). The difference is input pats are globbed on
1710 # scmutil.match(). The difference is input pats are globbed on
1711 # platforms without shell expansion (windows).
1711 # platforms without shell expansion (windows).
1712 pctx = repo[None]
1712 pctx = repo[None]
1713 match, pats = scmutil.matchandpats(pctx, pats, opts)
1713 match, pats = scmutil.matchandpats(pctx, pats, opts)
1714 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1714 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1715 if not slowpath:
1715 if not slowpath:
1716 for f in match.files():
1716 for f in match.files():
1717 if follow and f not in pctx:
1717 if follow and f not in pctx:
1718 # If the file exists, it may be a directory, so let it
1718 # If the file exists, it may be a directory, so let it
1719 # take the slow path.
1719 # take the slow path.
1720 if os.path.exists(repo.wjoin(f)):
1720 if os.path.exists(repo.wjoin(f)):
1721 slowpath = True
1721 slowpath = True
1722 continue
1722 continue
1723 else:
1723 else:
1724 raise util.Abort(_('cannot follow file not in parent '
1724 raise util.Abort(_('cannot follow file not in parent '
1725 'revision: "%s"') % f)
1725 'revision: "%s"') % f)
1726 filelog = repo.file(f)
1726 filelog = repo.file(f)
1727 if not filelog:
1727 if not filelog:
1728 # A zero count may be a directory or deleted file, so
1728 # A zero count may be a directory or deleted file, so
1729 # try to find matching entries on the slow path.
1729 # try to find matching entries on the slow path.
1730 if follow:
1730 if follow:
1731 raise util.Abort(
1731 raise util.Abort(
1732 _('cannot follow nonexistent file: "%s"') % f)
1732 _('cannot follow nonexistent file: "%s"') % f)
1733 slowpath = True
1733 slowpath = True
1734
1734
1735 # We decided to fall back to the slowpath because at least one
1735 # We decided to fall back to the slowpath because at least one
1736 # of the paths was not a file. Check to see if at least one of them
1736 # of the paths was not a file. Check to see if at least one of them
1737 # existed in history - in that case, we'll continue down the
1737 # existed in history - in that case, we'll continue down the
1738 # slowpath; otherwise, we can turn off the slowpath
1738 # slowpath; otherwise, we can turn off the slowpath
1739 if slowpath:
1739 if slowpath:
1740 for path in match.files():
1740 for path in match.files():
1741 if path == '.' or path in repo.store:
1741 if path == '.' or path in repo.store:
1742 break
1742 break
1743 else:
1743 else:
1744 slowpath = False
1744 slowpath = False
1745
1745
1746 fpats = ('_patsfollow', '_patsfollowfirst')
1746 fpats = ('_patsfollow', '_patsfollowfirst')
1747 fnopats = (('_ancestors', '_fancestors'),
1747 fnopats = (('_ancestors', '_fancestors'),
1748 ('_descendants', '_fdescendants'))
1748 ('_descendants', '_fdescendants'))
1749 if slowpath:
1749 if slowpath:
1750 # See walkchangerevs() slow path.
1750 # See walkchangerevs() slow path.
1751 #
1751 #
1752 # pats/include/exclude cannot be represented as separate
1752 # pats/include/exclude cannot be represented as separate
1753 # revset expressions as their filtering logic applies at file
1753 # revset expressions as their filtering logic applies at file
1754 # level. For instance "-I a -X a" matches a revision touching
1754 # level. For instance "-I a -X a" matches a revision touching
1755 # "a" and "b" while "file(a) and not file(b)" does
1755 # "a" and "b" while "file(a) and not file(b)" does
1756 # not. Besides, filesets are evaluated against the working
1756 # not. Besides, filesets are evaluated against the working
1757 # directory.
1757 # directory.
1758 matchargs = ['r:', 'd:relpath']
1758 matchargs = ['r:', 'd:relpath']
1759 for p in pats:
1759 for p in pats:
1760 matchargs.append('p:' + p)
1760 matchargs.append('p:' + p)
1761 for p in opts.get('include', []):
1761 for p in opts.get('include', []):
1762 matchargs.append('i:' + p)
1762 matchargs.append('i:' + p)
1763 for p in opts.get('exclude', []):
1763 for p in opts.get('exclude', []):
1764 matchargs.append('x:' + p)
1764 matchargs.append('x:' + p)
1765 matchargs = ','.join(('%r' % p) for p in matchargs)
1765 matchargs = ','.join(('%r' % p) for p in matchargs)
1766 opts['_matchfiles'] = matchargs
1766 opts['_matchfiles'] = matchargs
1767 if follow:
1767 if follow:
1768 opts[fnopats[0][followfirst]] = '.'
1768 opts[fnopats[0][followfirst]] = '.'
1769 else:
1769 else:
1770 if follow:
1770 if follow:
1771 if pats:
1771 if pats:
1772 # follow() revset interprets its file argument as a
1772 # follow() revset interprets its file argument as a
1773 # manifest entry, so use match.files(), not pats.
1773 # manifest entry, so use match.files(), not pats.
1774 opts[fpats[followfirst]] = list(match.files())
1774 opts[fpats[followfirst]] = list(match.files())
1775 else:
1775 else:
1776 op = fnopats[followdescendants][followfirst]
1776 op = fnopats[followdescendants][followfirst]
1777 opts[op] = 'rev(%d)' % startrev
1777 opts[op] = 'rev(%d)' % startrev
1778 else:
1778 else:
1779 opts['_patslog'] = list(pats)
1779 opts['_patslog'] = list(pats)
1780
1780
1781 filematcher = None
1781 filematcher = None
1782 if opts.get('patch') or opts.get('stat'):
1782 if opts.get('patch') or opts.get('stat'):
1783 # When following files, track renames via a special matcher.
1783 # When following files, track renames via a special matcher.
1784 # If we're forced to take the slowpath it means we're following
1784 # If we're forced to take the slowpath it means we're following
1785 # at least one pattern/directory, so don't bother with rename tracking.
1785 # at least one pattern/directory, so don't bother with rename tracking.
1786 if follow and not match.always() and not slowpath:
1786 if follow and not match.always() and not slowpath:
1787 # _makefollowlogfilematcher expects its files argument to be
1787 # _makefollowlogfilematcher expects its files argument to be
1788 # relative to the repo root, so use match.files(), not pats.
1788 # relative to the repo root, so use match.files(), not pats.
1789 filematcher = _makefollowlogfilematcher(repo, match.files(),
1789 filematcher = _makefollowlogfilematcher(repo, match.files(),
1790 followfirst)
1790 followfirst)
1791 else:
1791 else:
1792 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1792 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1793 if filematcher is None:
1793 if filematcher is None:
1794 filematcher = lambda rev: match
1794 filematcher = lambda rev: match
1795
1795
1796 expr = []
1796 expr = []
1797 for op, val in sorted(opts.iteritems()):
1797 for op, val in sorted(opts.iteritems()):
1798 if not val:
1798 if not val:
1799 continue
1799 continue
1800 if op not in opt2revset:
1800 if op not in opt2revset:
1801 continue
1801 continue
1802 revop, andor = opt2revset[op]
1802 revop, andor = opt2revset[op]
1803 if '%(val)' not in revop:
1803 if '%(val)' not in revop:
1804 expr.append(revop)
1804 expr.append(revop)
1805 else:
1805 else:
1806 if not isinstance(val, list):
1806 if not isinstance(val, list):
1807 e = revop % {'val': val}
1807 e = revop % {'val': val}
1808 else:
1808 else:
1809 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1809 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1810 expr.append(e)
1810 expr.append(e)
1811
1811
1812 if expr:
1812 if expr:
1813 expr = '(' + ' and '.join(expr) + ')'
1813 expr = '(' + ' and '.join(expr) + ')'
1814 else:
1814 else:
1815 expr = None
1815 expr = None
1816 return expr, filematcher
1816 return expr, filematcher
1817
1817
1818 def _logrevs(repo, opts):
1818 def _logrevs(repo, opts):
1819 # Default --rev value depends on --follow but --follow behaviour
1819 # Default --rev value depends on --follow but --follow behaviour
1820 # depends on revisions resolved from --rev...
1820 # depends on revisions resolved from --rev...
1821 follow = opts.get('follow') or opts.get('follow_first')
1821 follow = opts.get('follow') or opts.get('follow_first')
1822 if opts.get('rev'):
1822 if opts.get('rev'):
1823 revs = scmutil.revrange(repo, opts['rev'])
1823 revs = scmutil.revrange(repo, opts['rev'])
1824 elif follow and repo.dirstate.p1() == nullid:
1824 elif follow and repo.dirstate.p1() == nullid:
1825 revs = revset.baseset()
1825 revs = revset.baseset()
1826 elif follow:
1826 elif follow:
1827 revs = repo.revs('reverse(:.)')
1827 revs = repo.revs('reverse(:.)')
1828 else:
1828 else:
1829 revs = revset.spanset(repo)
1829 revs = revset.spanset(repo)
1830 revs.reverse()
1830 revs.reverse()
1831 return revs
1831 return revs
1832
1832
1833 def getgraphlogrevs(repo, pats, opts):
1833 def getgraphlogrevs(repo, pats, opts):
1834 """Return (revs, expr, filematcher) where revs is an iterable of
1834 """Return (revs, expr, filematcher) where revs is an iterable of
1835 revision numbers, expr is a revset string built from log options
1835 revision numbers, expr is a revset string built from log options
1836 and file patterns or None, and used to filter 'revs'. If --stat or
1836 and file patterns or None, and used to filter 'revs'. If --stat or
1837 --patch are not passed filematcher is None. Otherwise it is a
1837 --patch are not passed filematcher is None. Otherwise it is a
1838 callable taking a revision number and returning a match objects
1838 callable taking a revision number and returning a match objects
1839 filtering the files to be detailed when displaying the revision.
1839 filtering the files to be detailed when displaying the revision.
1840 """
1840 """
1841 limit = loglimit(opts)
1841 limit = loglimit(opts)
1842 revs = _logrevs(repo, opts)
1842 revs = _logrevs(repo, opts)
1843 if not revs:
1843 if not revs:
1844 return revset.baseset(), None, None
1844 return revset.baseset(), None, None
1845 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1845 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1846 if opts.get('rev'):
1846 if opts.get('rev'):
1847 # User-specified revs might be unsorted, but don't sort before
1847 # User-specified revs might be unsorted, but don't sort before
1848 # _makelogrevset because it might depend on the order of revs
1848 # _makelogrevset because it might depend on the order of revs
1849 revs.sort(reverse=True)
1849 revs.sort(reverse=True)
1850 if expr:
1850 if expr:
1851 # Revset matchers often operate faster on revisions in changelog
1851 # Revset matchers often operate faster on revisions in changelog
1852 # order, because most filters deal with the changelog.
1852 # order, because most filters deal with the changelog.
1853 revs.reverse()
1853 revs.reverse()
1854 matcher = revset.match(repo.ui, expr)
1854 matcher = revset.match(repo.ui, expr)
1855 # Revset matches can reorder revisions. "A or B" typically returns
1855 # Revset matches can reorder revisions. "A or B" typically returns
1856 # returns the revision matching A then the revision matching B. Sort
1856 # returns the revision matching A then the revision matching B. Sort
1857 # again to fix that.
1857 # again to fix that.
1858 revs = matcher(repo, revs)
1858 revs = matcher(repo, revs)
1859 revs.sort(reverse=True)
1859 revs.sort(reverse=True)
1860 if limit is not None:
1860 if limit is not None:
1861 limitedrevs = []
1861 limitedrevs = []
1862 for idx, rev in enumerate(revs):
1862 for idx, rev in enumerate(revs):
1863 if idx >= limit:
1863 if idx >= limit:
1864 break
1864 break
1865 limitedrevs.append(rev)
1865 limitedrevs.append(rev)
1866 revs = revset.baseset(limitedrevs)
1866 revs = revset.baseset(limitedrevs)
1867
1867
1868 return revs, expr, filematcher
1868 return revs, expr, filematcher
1869
1869
1870 def getlogrevs(repo, pats, opts):
1870 def getlogrevs(repo, pats, opts):
1871 """Return (revs, expr, filematcher) where revs is an iterable of
1871 """Return (revs, expr, filematcher) where revs is an iterable of
1872 revision numbers, expr is a revset string built from log options
1872 revision numbers, expr is a revset string built from log options
1873 and file patterns or None, and used to filter 'revs'. If --stat or
1873 and file patterns or None, and used to filter 'revs'. If --stat or
1874 --patch are not passed filematcher is None. Otherwise it is a
1874 --patch are not passed filematcher is None. Otherwise it is a
1875 callable taking a revision number and returning a match objects
1875 callable taking a revision number and returning a match objects
1876 filtering the files to be detailed when displaying the revision.
1876 filtering the files to be detailed when displaying the revision.
1877 """
1877 """
1878 limit = loglimit(opts)
1878 limit = loglimit(opts)
1879 revs = _logrevs(repo, opts)
1879 revs = _logrevs(repo, opts)
1880 if not revs:
1880 if not revs:
1881 return revset.baseset([]), None, None
1881 return revset.baseset([]), None, None
1882 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1882 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1883 if expr:
1883 if expr:
1884 # Revset matchers often operate faster on revisions in changelog
1884 # Revset matchers often operate faster on revisions in changelog
1885 # order, because most filters deal with the changelog.
1885 # order, because most filters deal with the changelog.
1886 if not opts.get('rev'):
1886 if not opts.get('rev'):
1887 revs.reverse()
1887 revs.reverse()
1888 matcher = revset.match(repo.ui, expr)
1888 matcher = revset.match(repo.ui, expr)
1889 # Revset matches can reorder revisions. "A or B" typically returns
1889 # Revset matches can reorder revisions. "A or B" typically returns
1890 # returns the revision matching A then the revision matching B. Sort
1890 # returns the revision matching A then the revision matching B. Sort
1891 # again to fix that.
1891 # again to fix that.
1892 revs = matcher(repo, revs)
1892 revs = matcher(repo, revs)
1893 if not opts.get('rev'):
1893 if not opts.get('rev'):
1894 revs.sort(reverse=True)
1894 revs.sort(reverse=True)
1895 if limit is not None:
1895 if limit is not None:
1896 count = 0
1896 count = 0
1897 limitedrevs = []
1897 limitedrevs = []
1898 it = iter(revs)
1898 it = iter(revs)
1899 while count < limit:
1899 while count < limit:
1900 try:
1900 try:
1901 limitedrevs.append(it.next())
1901 limitedrevs.append(it.next())
1902 except (StopIteration):
1902 except (StopIteration):
1903 break
1903 break
1904 count += 1
1904 count += 1
1905 revs = revset.baseset(limitedrevs)
1905 revs = revset.baseset(limitedrevs)
1906
1906
1907 return revs, expr, filematcher
1907 return revs, expr, filematcher
1908
1908
1909 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1909 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1910 filematcher=None):
1910 filematcher=None):
1911 seen, state = [], graphmod.asciistate()
1911 seen, state = [], graphmod.asciistate()
1912 for rev, type, ctx, parents in dag:
1912 for rev, type, ctx, parents in dag:
1913 char = 'o'
1913 char = 'o'
1914 if ctx.node() in showparents:
1914 if ctx.node() in showparents:
1915 char = '@'
1915 char = '@'
1916 elif ctx.obsolete():
1916 elif ctx.obsolete():
1917 char = 'x'
1917 char = 'x'
1918 copies = None
1918 copies = None
1919 if getrenamed and ctx.rev():
1919 if getrenamed and ctx.rev():
1920 copies = []
1920 copies = []
1921 for fn in ctx.files():
1921 for fn in ctx.files():
1922 rename = getrenamed(fn, ctx.rev())
1922 rename = getrenamed(fn, ctx.rev())
1923 if rename:
1923 if rename:
1924 copies.append((fn, rename[0]))
1924 copies.append((fn, rename[0]))
1925 revmatchfn = None
1925 revmatchfn = None
1926 if filematcher is not None:
1926 if filematcher is not None:
1927 revmatchfn = filematcher(ctx.rev())
1927 revmatchfn = filematcher(ctx.rev())
1928 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1928 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1929 lines = displayer.hunk.pop(rev).split('\n')
1929 lines = displayer.hunk.pop(rev).split('\n')
1930 if not lines[-1]:
1930 if not lines[-1]:
1931 del lines[-1]
1931 del lines[-1]
1932 displayer.flush(rev)
1932 displayer.flush(rev)
1933 edges = edgefn(type, char, lines, seen, rev, parents)
1933 edges = edgefn(type, char, lines, seen, rev, parents)
1934 for type, char, lines, coldata in edges:
1934 for type, char, lines, coldata in edges:
1935 graphmod.ascii(ui, state, type, char, lines, coldata)
1935 graphmod.ascii(ui, state, type, char, lines, coldata)
1936 displayer.close()
1936 displayer.close()
1937
1937
1938 def graphlog(ui, repo, *pats, **opts):
1938 def graphlog(ui, repo, *pats, **opts):
1939 # Parameters are identical to log command ones
1939 # Parameters are identical to log command ones
1940 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1940 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1941 revdag = graphmod.dagwalker(repo, revs)
1941 revdag = graphmod.dagwalker(repo, revs)
1942
1942
1943 getrenamed = None
1943 getrenamed = None
1944 if opts.get('copies'):
1944 if opts.get('copies'):
1945 endrev = None
1945 endrev = None
1946 if opts.get('rev'):
1946 if opts.get('rev'):
1947 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1947 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1948 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1948 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1949 displayer = show_changeset(ui, repo, opts, buffered=True)
1949 displayer = show_changeset(ui, repo, opts, buffered=True)
1950 showparents = [ctx.node() for ctx in repo[None].parents()]
1950 showparents = [ctx.node() for ctx in repo[None].parents()]
1951 displaygraph(ui, revdag, displayer, showparents,
1951 displaygraph(ui, revdag, displayer, showparents,
1952 graphmod.asciiedges, getrenamed, filematcher)
1952 graphmod.asciiedges, getrenamed, filematcher)
1953
1953
1954 def checkunsupportedgraphflags(pats, opts):
1954 def checkunsupportedgraphflags(pats, opts):
1955 for op in ["newest_first"]:
1955 for op in ["newest_first"]:
1956 if op in opts and opts[op]:
1956 if op in opts and opts[op]:
1957 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1957 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1958 % op.replace("_", "-"))
1958 % op.replace("_", "-"))
1959
1959
1960 def graphrevs(repo, nodes, opts):
1960 def graphrevs(repo, nodes, opts):
1961 limit = loglimit(opts)
1961 limit = loglimit(opts)
1962 nodes.reverse()
1962 nodes.reverse()
1963 if limit is not None:
1963 if limit is not None:
1964 nodes = nodes[:limit]
1964 nodes = nodes[:limit]
1965 return graphmod.nodes(repo, nodes)
1965 return graphmod.nodes(repo, nodes)
1966
1966
1967 def add(ui, repo, match, prefix, explicitonly, **opts):
1967 def add(ui, repo, match, prefix, explicitonly, **opts):
1968 join = lambda f: os.path.join(prefix, f)
1968 join = lambda f: os.path.join(prefix, f)
1969 bad = []
1969 bad = []
1970 oldbad = match.bad
1970 oldbad = match.bad
1971 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1971 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1972 names = []
1972 names = []
1973 wctx = repo[None]
1973 wctx = repo[None]
1974 cca = None
1974 cca = None
1975 abort, warn = scmutil.checkportabilityalert(ui)
1975 abort, warn = scmutil.checkportabilityalert(ui)
1976 if abort or warn:
1976 if abort or warn:
1977 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1977 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1978 for f in wctx.walk(match):
1978 for f in wctx.walk(match):
1979 exact = match.exact(f)
1979 exact = match.exact(f)
1980 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1980 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1981 if cca:
1981 if cca:
1982 cca(f)
1982 cca(f)
1983 names.append(f)
1983 names.append(f)
1984 if ui.verbose or not exact:
1984 if ui.verbose or not exact:
1985 ui.status(_('adding %s\n') % match.rel(f))
1985 ui.status(_('adding %s\n') % match.rel(f))
1986
1986
1987 for subpath in sorted(wctx.substate):
1987 for subpath in sorted(wctx.substate):
1988 sub = wctx.sub(subpath)
1988 sub = wctx.sub(subpath)
1989 try:
1989 try:
1990 submatch = matchmod.narrowmatcher(subpath, match)
1990 submatch = matchmod.narrowmatcher(subpath, match)
1991 if opts.get('subrepos'):
1991 if opts.get('subrepos'):
1992 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1992 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1993 else:
1993 else:
1994 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1994 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1995 except error.LookupError:
1995 except error.LookupError:
1996 ui.status(_("skipping missing subrepository: %s\n")
1996 ui.status(_("skipping missing subrepository: %s\n")
1997 % join(subpath))
1997 % join(subpath))
1998
1998
1999 if not opts.get('dry_run'):
1999 if not opts.get('dry_run'):
2000 rejected = wctx.add(names, prefix)
2000 rejected = wctx.add(names, prefix)
2001 bad.extend(f for f in rejected if f in match.files())
2001 bad.extend(f for f in rejected if f in match.files())
2002 return bad
2002 return bad
2003
2003
2004 def forget(ui, repo, match, prefix, explicitonly):
2004 def forget(ui, repo, match, prefix, explicitonly):
2005 join = lambda f: os.path.join(prefix, f)
2005 join = lambda f: os.path.join(prefix, f)
2006 bad = []
2006 bad = []
2007 oldbad = match.bad
2007 oldbad = match.bad
2008 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2008 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2009 wctx = repo[None]
2009 wctx = repo[None]
2010 forgot = []
2010 forgot = []
2011 s = repo.status(match=match, clean=True)
2011 s = repo.status(match=match, clean=True)
2012 forget = sorted(s[0] + s[1] + s[3] + s[6])
2012 forget = sorted(s[0] + s[1] + s[3] + s[6])
2013 if explicitonly:
2013 if explicitonly:
2014 forget = [f for f in forget if match.exact(f)]
2014 forget = [f for f in forget if match.exact(f)]
2015
2015
2016 for subpath in sorted(wctx.substate):
2016 for subpath in sorted(wctx.substate):
2017 sub = wctx.sub(subpath)
2017 sub = wctx.sub(subpath)
2018 try:
2018 try:
2019 submatch = matchmod.narrowmatcher(subpath, match)
2019 submatch = matchmod.narrowmatcher(subpath, match)
2020 subbad, subforgot = sub.forget(submatch, prefix)
2020 subbad, subforgot = sub.forget(submatch, prefix)
2021 bad.extend([subpath + '/' + f for f in subbad])
2021 bad.extend([subpath + '/' + f for f in subbad])
2022 forgot.extend([subpath + '/' + f for f in subforgot])
2022 forgot.extend([subpath + '/' + f for f in subforgot])
2023 except error.LookupError:
2023 except error.LookupError:
2024 ui.status(_("skipping missing subrepository: %s\n")
2024 ui.status(_("skipping missing subrepository: %s\n")
2025 % join(subpath))
2025 % join(subpath))
2026
2026
2027 if not explicitonly:
2027 if not explicitonly:
2028 for f in match.files():
2028 for f in match.files():
2029 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2029 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2030 if f not in forgot:
2030 if f not in forgot:
2031 if repo.wvfs.exists(f):
2031 if repo.wvfs.exists(f):
2032 ui.warn(_('not removing %s: '
2032 ui.warn(_('not removing %s: '
2033 'file is already untracked\n')
2033 'file is already untracked\n')
2034 % match.rel(f))
2034 % match.rel(f))
2035 bad.append(f)
2035 bad.append(f)
2036
2036
2037 for f in forget:
2037 for f in forget:
2038 if ui.verbose or not match.exact(f):
2038 if ui.verbose or not match.exact(f):
2039 ui.status(_('removing %s\n') % match.rel(f))
2039 ui.status(_('removing %s\n') % match.rel(f))
2040
2040
2041 rejected = wctx.forget(forget, prefix)
2041 rejected = wctx.forget(forget, prefix)
2042 bad.extend(f for f in rejected if f in match.files())
2042 bad.extend(f for f in rejected if f in match.files())
2043 forgot.extend(f for f in forget if f not in rejected)
2043 forgot.extend(f for f in forget if f not in rejected)
2044 return bad, forgot
2044 return bad, forgot
2045
2045
2046 def remove(ui, repo, m, prefix, after, force, subrepos):
2046 def remove(ui, repo, m, prefix, after, force, subrepos):
2047 join = lambda f: os.path.join(prefix, f)
2047 join = lambda f: os.path.join(prefix, f)
2048 ret = 0
2048 ret = 0
2049 s = repo.status(match=m, clean=True)
2049 s = repo.status(match=m, clean=True)
2050 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2050 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2051
2051
2052 wctx = repo[None]
2052 wctx = repo[None]
2053
2053
2054 for subpath in sorted(wctx.substate):
2054 for subpath in sorted(wctx.substate):
2055 def matchessubrepo(matcher, subpath):
2055 def matchessubrepo(matcher, subpath):
2056 if matcher.exact(subpath):
2056 if matcher.exact(subpath):
2057 return True
2057 return True
2058 for f in matcher.files():
2058 for f in matcher.files():
2059 if f.startswith(subpath):
2059 if f.startswith(subpath):
2060 return True
2060 return True
2061 return False
2061 return False
2062
2062
2063 if subrepos or matchessubrepo(m, subpath):
2063 if subrepos or matchessubrepo(m, subpath):
2064 sub = wctx.sub(subpath)
2064 sub = wctx.sub(subpath)
2065 try:
2065 try:
2066 submatch = matchmod.narrowmatcher(subpath, m)
2066 submatch = matchmod.narrowmatcher(subpath, m)
2067 if sub.removefiles(submatch, prefix, after, force, subrepos):
2067 if sub.removefiles(submatch, prefix, after, force, subrepos):
2068 ret = 1
2068 ret = 1
2069 except error.LookupError:
2069 except error.LookupError:
2070 ui.status(_("skipping missing subrepository: %s\n")
2070 ui.status(_("skipping missing subrepository: %s\n")
2071 % join(subpath))
2071 % join(subpath))
2072
2072
2073 # warn about failure to delete explicit files/dirs
2073 # warn about failure to delete explicit files/dirs
2074 for f in m.files():
2074 for f in m.files():
2075 def insubrepo():
2075 def insubrepo():
2076 for subpath in wctx.substate:
2076 for subpath in wctx.substate:
2077 if f.startswith(subpath):
2077 if f.startswith(subpath):
2078 return True
2078 return True
2079 return False
2079 return False
2080
2080
2081 if f in repo.dirstate or f in wctx.dirs() or f == '.' or insubrepo():
2081 if f in repo.dirstate or f in wctx.dirs() or f == '.' or insubrepo():
2082 continue
2082 continue
2083
2083
2084 if repo.wvfs.exists(f):
2084 if repo.wvfs.exists(f):
2085 if repo.wvfs.isdir(f):
2085 if repo.wvfs.isdir(f):
2086 ui.warn(_('not removing %s: no tracked files\n')
2086 ui.warn(_('not removing %s: no tracked files\n')
2087 % m.rel(f))
2087 % m.rel(f))
2088 else:
2088 else:
2089 ui.warn(_('not removing %s: file is untracked\n')
2089 ui.warn(_('not removing %s: file is untracked\n')
2090 % m.rel(f))
2090 % m.rel(f))
2091 # missing files will generate a warning elsewhere
2091 # missing files will generate a warning elsewhere
2092 ret = 1
2092 ret = 1
2093
2093
2094 if force:
2094 if force:
2095 list = modified + deleted + clean + added
2095 list = modified + deleted + clean + added
2096 elif after:
2096 elif after:
2097 list = deleted
2097 list = deleted
2098 for f in modified + added + clean:
2098 for f in modified + added + clean:
2099 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2099 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2100 ret = 1
2100 ret = 1
2101 else:
2101 else:
2102 list = deleted + clean
2102 list = deleted + clean
2103 for f in modified:
2103 for f in modified:
2104 ui.warn(_('not removing %s: file is modified (use -f'
2104 ui.warn(_('not removing %s: file is modified (use -f'
2105 ' to force removal)\n') % m.rel(f))
2105 ' to force removal)\n') % m.rel(f))
2106 ret = 1
2106 ret = 1
2107 for f in added:
2107 for f in added:
2108 ui.warn(_('not removing %s: file has been marked for add'
2108 ui.warn(_('not removing %s: file has been marked for add'
2109 ' (use forget to undo)\n') % m.rel(f))
2109 ' (use forget to undo)\n') % m.rel(f))
2110 ret = 1
2110 ret = 1
2111
2111
2112 for f in sorted(list):
2112 for f in sorted(list):
2113 if ui.verbose or not m.exact(f):
2113 if ui.verbose or not m.exact(f):
2114 ui.status(_('removing %s\n') % m.rel(f))
2114 ui.status(_('removing %s\n') % m.rel(f))
2115
2115
2116 wlock = repo.wlock()
2116 wlock = repo.wlock()
2117 try:
2117 try:
2118 if not after:
2118 if not after:
2119 for f in list:
2119 for f in list:
2120 if f in added:
2120 if f in added:
2121 continue # we never unlink added files on remove
2121 continue # we never unlink added files on remove
2122 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2122 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2123 repo[None].forget(list)
2123 repo[None].forget(list)
2124 finally:
2124 finally:
2125 wlock.release()
2125 wlock.release()
2126
2126
2127 return ret
2127 return ret
2128
2128
2129 def cat(ui, repo, ctx, matcher, prefix, **opts):
2129 def cat(ui, repo, ctx, matcher, prefix, **opts):
2130 err = 1
2130 err = 1
2131
2131
2132 def write(path):
2132 def write(path):
2133 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2133 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2134 pathname=os.path.join(prefix, path))
2134 pathname=os.path.join(prefix, path))
2135 data = ctx[path].data()
2135 data = ctx[path].data()
2136 if opts.get('decode'):
2136 if opts.get('decode'):
2137 data = repo.wwritedata(path, data)
2137 data = repo.wwritedata(path, data)
2138 fp.write(data)
2138 fp.write(data)
2139 fp.close()
2139 fp.close()
2140
2140
2141 # Automation often uses hg cat on single files, so special case it
2141 # Automation often uses hg cat on single files, so special case it
2142 # for performance to avoid the cost of parsing the manifest.
2142 # for performance to avoid the cost of parsing the manifest.
2143 if len(matcher.files()) == 1 and not matcher.anypats():
2143 if len(matcher.files()) == 1 and not matcher.anypats():
2144 file = matcher.files()[0]
2144 file = matcher.files()[0]
2145 mf = repo.manifest
2145 mf = repo.manifest
2146 mfnode = ctx._changeset[0]
2146 mfnode = ctx._changeset[0]
2147 if mf.find(mfnode, file)[0]:
2147 if mf.find(mfnode, file)[0]:
2148 write(file)
2148 write(file)
2149 return 0
2149 return 0
2150
2150
2151 # Don't warn about "missing" files that are really in subrepos
2151 # Don't warn about "missing" files that are really in subrepos
2152 bad = matcher.bad
2152 bad = matcher.bad
2153
2153
2154 def badfn(path, msg):
2154 def badfn(path, msg):
2155 for subpath in ctx.substate:
2155 for subpath in ctx.substate:
2156 if path.startswith(subpath):
2156 if path.startswith(subpath):
2157 return
2157 return
2158 bad(path, msg)
2158 bad(path, msg)
2159
2159
2160 matcher.bad = badfn
2160 matcher.bad = badfn
2161
2161
2162 for abs in ctx.walk(matcher):
2162 for abs in ctx.walk(matcher):
2163 write(abs)
2163 write(abs)
2164 err = 0
2164 err = 0
2165
2165
2166 matcher.bad = bad
2166 matcher.bad = bad
2167
2167
2168 for subpath in sorted(ctx.substate):
2168 for subpath in sorted(ctx.substate):
2169 sub = ctx.sub(subpath)
2169 sub = ctx.sub(subpath)
2170 try:
2170 try:
2171 submatch = matchmod.narrowmatcher(subpath, matcher)
2171 submatch = matchmod.narrowmatcher(subpath, matcher)
2172
2172
2173 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2173 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2174 **opts):
2174 **opts):
2175 err = 0
2175 err = 0
2176 except error.RepoLookupError:
2176 except error.RepoLookupError:
2177 ui.status(_("skipping missing subrepository: %s\n")
2177 ui.status(_("skipping missing subrepository: %s\n")
2178 % os.path.join(prefix, subpath))
2178 % os.path.join(prefix, subpath))
2179
2179
2180 return err
2180 return err
2181
2181
2182 def commit(ui, repo, commitfunc, pats, opts):
2182 def commit(ui, repo, commitfunc, pats, opts):
2183 '''commit the specified files or all outstanding changes'''
2183 '''commit the specified files or all outstanding changes'''
2184 date = opts.get('date')
2184 date = opts.get('date')
2185 if date:
2185 if date:
2186 opts['date'] = util.parsedate(date)
2186 opts['date'] = util.parsedate(date)
2187 message = logmessage(ui, opts)
2187 message = logmessage(ui, opts)
2188 matcher = scmutil.match(repo[None], pats, opts)
2188 matcher = scmutil.match(repo[None], pats, opts)
2189
2189
2190 # extract addremove carefully -- this function can be called from a command
2190 # extract addremove carefully -- this function can be called from a command
2191 # that doesn't support addremove
2191 # that doesn't support addremove
2192 if opts.get('addremove'):
2192 if opts.get('addremove'):
2193 if scmutil.addremove(repo, matcher, "", opts) != 0:
2193 if scmutil.addremove(repo, matcher, "", opts) != 0:
2194 raise util.Abort(
2194 raise util.Abort(
2195 _("failed to mark all new/missing files as added/removed"))
2195 _("failed to mark all new/missing files as added/removed"))
2196
2196
2197 return commitfunc(ui, repo, message, matcher, opts)
2197 return commitfunc(ui, repo, message, matcher, opts)
2198
2198
2199 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2199 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2200 # amend will reuse the existing user if not specified, but the obsolete
2200 # amend will reuse the existing user if not specified, but the obsolete
2201 # marker creation requires that the current user's name is specified.
2201 # marker creation requires that the current user's name is specified.
2202 if obsolete._enabled:
2202 if obsolete._enabled:
2203 ui.username() # raise exception if username not set
2203 ui.username() # raise exception if username not set
2204
2204
2205 ui.note(_('amending changeset %s\n') % old)
2205 ui.note(_('amending changeset %s\n') % old)
2206 base = old.p1()
2206 base = old.p1()
2207
2207
2208 wlock = lock = newid = None
2208 wlock = lock = newid = None
2209 try:
2209 try:
2210 wlock = repo.wlock()
2210 wlock = repo.wlock()
2211 lock = repo.lock()
2211 lock = repo.lock()
2212 tr = repo.transaction('amend')
2212 tr = repo.transaction('amend')
2213 try:
2213 try:
2214 # See if we got a message from -m or -l, if not, open the editor
2214 # See if we got a message from -m or -l, if not, open the editor
2215 # with the message of the changeset to amend
2215 # with the message of the changeset to amend
2216 message = logmessage(ui, opts)
2216 message = logmessage(ui, opts)
2217 # ensure logfile does not conflict with later enforcement of the
2217 # ensure logfile does not conflict with later enforcement of the
2218 # message. potential logfile content has been processed by
2218 # message. potential logfile content has been processed by
2219 # `logmessage` anyway.
2219 # `logmessage` anyway.
2220 opts.pop('logfile')
2220 opts.pop('logfile')
2221 # First, do a regular commit to record all changes in the working
2221 # First, do a regular commit to record all changes in the working
2222 # directory (if there are any)
2222 # directory (if there are any)
2223 ui.callhooks = False
2223 ui.callhooks = False
2224 currentbookmark = repo._bookmarkcurrent
2224 currentbookmark = repo._bookmarkcurrent
2225 try:
2225 try:
2226 repo._bookmarkcurrent = None
2226 repo._bookmarkcurrent = None
2227 opts['message'] = 'temporary amend commit for %s' % old
2227 opts['message'] = 'temporary amend commit for %s' % old
2228 node = commit(ui, repo, commitfunc, pats, opts)
2228 node = commit(ui, repo, commitfunc, pats, opts)
2229 finally:
2229 finally:
2230 repo._bookmarkcurrent = currentbookmark
2230 repo._bookmarkcurrent = currentbookmark
2231 ui.callhooks = True
2231 ui.callhooks = True
2232 ctx = repo[node]
2232 ctx = repo[node]
2233
2233
2234 # Participating changesets:
2234 # Participating changesets:
2235 #
2235 #
2236 # node/ctx o - new (intermediate) commit that contains changes
2236 # node/ctx o - new (intermediate) commit that contains changes
2237 # | from working dir to go into amending commit
2237 # | from working dir to go into amending commit
2238 # | (or a workingctx if there were no changes)
2238 # | (or a workingctx if there were no changes)
2239 # |
2239 # |
2240 # old o - changeset to amend
2240 # old o - changeset to amend
2241 # |
2241 # |
2242 # base o - parent of amending changeset
2242 # base o - parent of amending changeset
2243
2243
2244 # Update extra dict from amended commit (e.g. to preserve graft
2244 # Update extra dict from amended commit (e.g. to preserve graft
2245 # source)
2245 # source)
2246 extra.update(old.extra())
2246 extra.update(old.extra())
2247
2247
2248 # Also update it from the intermediate commit or from the wctx
2248 # Also update it from the intermediate commit or from the wctx
2249 extra.update(ctx.extra())
2249 extra.update(ctx.extra())
2250
2250
2251 if len(old.parents()) > 1:
2251 if len(old.parents()) > 1:
2252 # ctx.files() isn't reliable for merges, so fall back to the
2252 # ctx.files() isn't reliable for merges, so fall back to the
2253 # slower repo.status() method
2253 # slower repo.status() method
2254 files = set([fn for st in repo.status(base, old)[:3]
2254 files = set([fn for st in repo.status(base, old)[:3]
2255 for fn in st])
2255 for fn in st])
2256 else:
2256 else:
2257 files = set(old.files())
2257 files = set(old.files())
2258
2258
2259 # Second, we use either the commit we just did, or if there were no
2259 # Second, we use either the commit we just did, or if there were no
2260 # changes the parent of the working directory as the version of the
2260 # changes the parent of the working directory as the version of the
2261 # files in the final amend commit
2261 # files in the final amend commit
2262 if node:
2262 if node:
2263 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2263 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2264
2264
2265 user = ctx.user()
2265 user = ctx.user()
2266 date = ctx.date()
2266 date = ctx.date()
2267 # Recompute copies (avoid recording a -> b -> a)
2267 # Recompute copies (avoid recording a -> b -> a)
2268 copied = copies.pathcopies(base, ctx)
2268 copied = copies.pathcopies(base, ctx)
2269
2269
2270 # Prune files which were reverted by the updates: if old
2270 # Prune files which were reverted by the updates: if old
2271 # introduced file X and our intermediate commit, node,
2271 # introduced file X and our intermediate commit, node,
2272 # renamed that file, then those two files are the same and
2272 # renamed that file, then those two files are the same and
2273 # we can discard X from our list of files. Likewise if X
2273 # we can discard X from our list of files. Likewise if X
2274 # was deleted, it's no longer relevant
2274 # was deleted, it's no longer relevant
2275 files.update(ctx.files())
2275 files.update(ctx.files())
2276
2276
2277 def samefile(f):
2277 def samefile(f):
2278 if f in ctx.manifest():
2278 if f in ctx.manifest():
2279 a = ctx.filectx(f)
2279 a = ctx.filectx(f)
2280 if f in base.manifest():
2280 if f in base.manifest():
2281 b = base.filectx(f)
2281 b = base.filectx(f)
2282 return (not a.cmp(b)
2282 return (not a.cmp(b)
2283 and a.flags() == b.flags())
2283 and a.flags() == b.flags())
2284 else:
2284 else:
2285 return False
2285 return False
2286 else:
2286 else:
2287 return f not in base.manifest()
2287 return f not in base.manifest()
2288 files = [f for f in files if not samefile(f)]
2288 files = [f for f in files if not samefile(f)]
2289
2289
2290 def filectxfn(repo, ctx_, path):
2290 def filectxfn(repo, ctx_, path):
2291 try:
2291 try:
2292 fctx = ctx[path]
2292 fctx = ctx[path]
2293 flags = fctx.flags()
2293 flags = fctx.flags()
2294 mctx = context.memfilectx(repo,
2294 mctx = context.memfilectx(repo,
2295 fctx.path(), fctx.data(),
2295 fctx.path(), fctx.data(),
2296 islink='l' in flags,
2296 islink='l' in flags,
2297 isexec='x' in flags,
2297 isexec='x' in flags,
2298 copied=copied.get(path))
2298 copied=copied.get(path))
2299 return mctx
2299 return mctx
2300 except KeyError:
2300 except KeyError:
2301 return None
2301 return None
2302 else:
2302 else:
2303 ui.note(_('copying changeset %s to %s\n') % (old, base))
2303 ui.note(_('copying changeset %s to %s\n') % (old, base))
2304
2304
2305 # Use version of files as in the old cset
2305 # Use version of files as in the old cset
2306 def filectxfn(repo, ctx_, path):
2306 def filectxfn(repo, ctx_, path):
2307 try:
2307 try:
2308 return old.filectx(path)
2308 return old.filectx(path)
2309 except KeyError:
2309 except KeyError:
2310 return None
2310 return None
2311
2311
2312 user = opts.get('user') or old.user()
2312 user = opts.get('user') or old.user()
2313 date = opts.get('date') or old.date()
2313 date = opts.get('date') or old.date()
2314 editform = mergeeditform(old, 'commit.amend')
2314 editform = mergeeditform(old, 'commit.amend')
2315 editor = getcommiteditor(editform=editform, **opts)
2315 editor = getcommiteditor(editform=editform, **opts)
2316 if not message:
2316 if not message:
2317 editor = getcommiteditor(edit=True, editform=editform)
2317 editor = getcommiteditor(edit=True, editform=editform)
2318 message = old.description()
2318 message = old.description()
2319
2319
2320 pureextra = extra.copy()
2320 pureextra = extra.copy()
2321 extra['amend_source'] = old.hex()
2321 extra['amend_source'] = old.hex()
2322
2322
2323 new = context.memctx(repo,
2323 new = context.memctx(repo,
2324 parents=[base.node(), old.p2().node()],
2324 parents=[base.node(), old.p2().node()],
2325 text=message,
2325 text=message,
2326 files=files,
2326 files=files,
2327 filectxfn=filectxfn,
2327 filectxfn=filectxfn,
2328 user=user,
2328 user=user,
2329 date=date,
2329 date=date,
2330 extra=extra,
2330 extra=extra,
2331 editor=editor)
2331 editor=editor)
2332
2332
2333 newdesc = changelog.stripdesc(new.description())
2333 newdesc = changelog.stripdesc(new.description())
2334 if ((not node)
2334 if ((not node)
2335 and newdesc == old.description()
2335 and newdesc == old.description()
2336 and user == old.user()
2336 and user == old.user()
2337 and date == old.date()
2337 and date == old.date()
2338 and pureextra == old.extra()):
2338 and pureextra == old.extra()):
2339 # nothing changed. continuing here would create a new node
2339 # nothing changed. continuing here would create a new node
2340 # anyway because of the amend_source noise.
2340 # anyway because of the amend_source noise.
2341 #
2341 #
2342 # This not what we expect from amend.
2342 # This not what we expect from amend.
2343 return old.node()
2343 return old.node()
2344
2344
2345 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2345 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2346 try:
2346 try:
2347 if opts.get('secret'):
2347 if opts.get('secret'):
2348 commitphase = 'secret'
2348 commitphase = 'secret'
2349 else:
2349 else:
2350 commitphase = old.phase()
2350 commitphase = old.phase()
2351 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2351 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2352 newid = repo.commitctx(new)
2352 newid = repo.commitctx(new)
2353 finally:
2353 finally:
2354 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2354 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2355 if newid != old.node():
2355 if newid != old.node():
2356 # Reroute the working copy parent to the new changeset
2356 # Reroute the working copy parent to the new changeset
2357 repo.setparents(newid, nullid)
2357 repo.setparents(newid, nullid)
2358
2358
2359 # Move bookmarks from old parent to amend commit
2359 # Move bookmarks from old parent to amend commit
2360 bms = repo.nodebookmarks(old.node())
2360 bms = repo.nodebookmarks(old.node())
2361 if bms:
2361 if bms:
2362 marks = repo._bookmarks
2362 marks = repo._bookmarks
2363 for bm in bms:
2363 for bm in bms:
2364 marks[bm] = newid
2364 marks[bm] = newid
2365 marks.write()
2365 marks.write()
2366 #commit the whole amend process
2366 #commit the whole amend process
2367 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2367 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2368 if createmarkers and newid != old.node():
2368 if createmarkers and newid != old.node():
2369 # mark the new changeset as successor of the rewritten one
2369 # mark the new changeset as successor of the rewritten one
2370 new = repo[newid]
2370 new = repo[newid]
2371 obs = [(old, (new,))]
2371 obs = [(old, (new,))]
2372 if node:
2372 if node:
2373 obs.append((ctx, ()))
2373 obs.append((ctx, ()))
2374
2374
2375 obsolete.createmarkers(repo, obs)
2375 obsolete.createmarkers(repo, obs)
2376 tr.close()
2376 tr.close()
2377 finally:
2377 finally:
2378 tr.release()
2378 tr.release()
2379 if not createmarkers and newid != old.node():
2379 if not createmarkers and newid != old.node():
2380 # Strip the intermediate commit (if there was one) and the amended
2380 # Strip the intermediate commit (if there was one) and the amended
2381 # commit
2381 # commit
2382 if node:
2382 if node:
2383 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2383 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2384 ui.note(_('stripping amended changeset %s\n') % old)
2384 ui.note(_('stripping amended changeset %s\n') % old)
2385 repair.strip(ui, repo, old.node(), topic='amend-backup')
2385 repair.strip(ui, repo, old.node(), topic='amend-backup')
2386 finally:
2386 finally:
2387 if newid is None:
2387 if newid is None:
2388 repo.dirstate.invalidate()
2388 repo.dirstate.invalidate()
2389 lockmod.release(lock, wlock)
2389 lockmod.release(lock, wlock)
2390 return newid
2390 return newid
2391
2391
2392 def commiteditor(repo, ctx, subs, editform=''):
2392 def commiteditor(repo, ctx, subs, editform=''):
2393 if ctx.description():
2393 if ctx.description():
2394 return ctx.description()
2394 return ctx.description()
2395 return commitforceeditor(repo, ctx, subs, editform=editform)
2395 return commitforceeditor(repo, ctx, subs, editform=editform)
2396
2396
2397 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2397 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2398 editform=''):
2398 editform=''):
2399 if not extramsg:
2399 if not extramsg:
2400 extramsg = _("Leave message empty to abort commit.")
2400 extramsg = _("Leave message empty to abort commit.")
2401
2401
2402 forms = [e for e in editform.split('.') if e]
2402 forms = [e for e in editform.split('.') if e]
2403 forms.insert(0, 'changeset')
2403 forms.insert(0, 'changeset')
2404 while forms:
2404 while forms:
2405 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2405 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2406 if tmpl:
2406 if tmpl:
2407 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2407 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2408 break
2408 break
2409 forms.pop()
2409 forms.pop()
2410 else:
2410 else:
2411 committext = buildcommittext(repo, ctx, subs, extramsg)
2411 committext = buildcommittext(repo, ctx, subs, extramsg)
2412
2412
2413 # run editor in the repository root
2413 # run editor in the repository root
2414 olddir = os.getcwd()
2414 olddir = os.getcwd()
2415 os.chdir(repo.root)
2415 os.chdir(repo.root)
2416 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2416 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2417 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2417 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2418 os.chdir(olddir)
2418 os.chdir(olddir)
2419
2419
2420 if finishdesc:
2420 if finishdesc:
2421 text = finishdesc(text)
2421 text = finishdesc(text)
2422 if not text.strip():
2422 if not text.strip():
2423 raise util.Abort(_("empty commit message"))
2423 raise util.Abort(_("empty commit message"))
2424
2424
2425 return text
2425 return text
2426
2426
2427 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2427 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2428 ui = repo.ui
2428 ui = repo.ui
2429 tmpl, mapfile = gettemplate(ui, tmpl, None)
2429 tmpl, mapfile = gettemplate(ui, tmpl, None)
2430
2430
2431 try:
2431 try:
2432 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2432 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2433 except SyntaxError, inst:
2433 except SyntaxError, inst:
2434 raise util.Abort(inst.args[0])
2434 raise util.Abort(inst.args[0])
2435
2435
2436 for k, v in repo.ui.configitems('committemplate'):
2436 for k, v in repo.ui.configitems('committemplate'):
2437 if k != 'changeset':
2437 if k != 'changeset':
2438 t.t.cache[k] = v
2438 t.t.cache[k] = v
2439
2439
2440 if not extramsg:
2440 if not extramsg:
2441 extramsg = '' # ensure that extramsg is string
2441 extramsg = '' # ensure that extramsg is string
2442
2442
2443 ui.pushbuffer()
2443 ui.pushbuffer()
2444 t.show(ctx, extramsg=extramsg)
2444 t.show(ctx, extramsg=extramsg)
2445 return ui.popbuffer()
2445 return ui.popbuffer()
2446
2446
2447 def buildcommittext(repo, ctx, subs, extramsg):
2447 def buildcommittext(repo, ctx, subs, extramsg):
2448 edittext = []
2448 edittext = []
2449 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2449 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2450 if ctx.description():
2450 if ctx.description():
2451 edittext.append(ctx.description())
2451 edittext.append(ctx.description())
2452 edittext.append("")
2452 edittext.append("")
2453 edittext.append("") # Empty line between message and comments.
2453 edittext.append("") # Empty line between message and comments.
2454 edittext.append(_("HG: Enter commit message."
2454 edittext.append(_("HG: Enter commit message."
2455 " Lines beginning with 'HG:' are removed."))
2455 " Lines beginning with 'HG:' are removed."))
2456 edittext.append("HG: %s" % extramsg)
2456 edittext.append("HG: %s" % extramsg)
2457 edittext.append("HG: --")
2457 edittext.append("HG: --")
2458 edittext.append(_("HG: user: %s") % ctx.user())
2458 edittext.append(_("HG: user: %s") % ctx.user())
2459 if ctx.p2():
2459 if ctx.p2():
2460 edittext.append(_("HG: branch merge"))
2460 edittext.append(_("HG: branch merge"))
2461 if ctx.branch():
2461 if ctx.branch():
2462 edittext.append(_("HG: branch '%s'") % ctx.branch())
2462 edittext.append(_("HG: branch '%s'") % ctx.branch())
2463 if bookmarks.iscurrent(repo):
2463 if bookmarks.iscurrent(repo):
2464 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2464 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2465 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2465 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2466 edittext.extend([_("HG: added %s") % f for f in added])
2466 edittext.extend([_("HG: added %s") % f for f in added])
2467 edittext.extend([_("HG: changed %s") % f for f in modified])
2467 edittext.extend([_("HG: changed %s") % f for f in modified])
2468 edittext.extend([_("HG: removed %s") % f for f in removed])
2468 edittext.extend([_("HG: removed %s") % f for f in removed])
2469 if not added and not modified and not removed:
2469 if not added and not modified and not removed:
2470 edittext.append(_("HG: no files changed"))
2470 edittext.append(_("HG: no files changed"))
2471 edittext.append("")
2471 edittext.append("")
2472
2472
2473 return "\n".join(edittext)
2473 return "\n".join(edittext)
2474
2474
2475 def commitstatus(repo, node, branch, bheads=None, opts={}):
2475 def commitstatus(repo, node, branch, bheads=None, opts={}):
2476 ctx = repo[node]
2476 ctx = repo[node]
2477 parents = ctx.parents()
2477 parents = ctx.parents()
2478
2478
2479 if (not opts.get('amend') and bheads and node not in bheads and not
2479 if (not opts.get('amend') and bheads and node not in bheads and not
2480 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2480 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2481 repo.ui.status(_('created new head\n'))
2481 repo.ui.status(_('created new head\n'))
2482 # The message is not printed for initial roots. For the other
2482 # The message is not printed for initial roots. For the other
2483 # changesets, it is printed in the following situations:
2483 # changesets, it is printed in the following situations:
2484 #
2484 #
2485 # Par column: for the 2 parents with ...
2485 # Par column: for the 2 parents with ...
2486 # N: null or no parent
2486 # N: null or no parent
2487 # B: parent is on another named branch
2487 # B: parent is on another named branch
2488 # C: parent is a regular non head changeset
2488 # C: parent is a regular non head changeset
2489 # H: parent was a branch head of the current branch
2489 # H: parent was a branch head of the current branch
2490 # Msg column: whether we print "created new head" message
2490 # Msg column: whether we print "created new head" message
2491 # In the following, it is assumed that there already exists some
2491 # In the following, it is assumed that there already exists some
2492 # initial branch heads of the current branch, otherwise nothing is
2492 # initial branch heads of the current branch, otherwise nothing is
2493 # printed anyway.
2493 # printed anyway.
2494 #
2494 #
2495 # Par Msg Comment
2495 # Par Msg Comment
2496 # N N y additional topo root
2496 # N N y additional topo root
2497 #
2497 #
2498 # B N y additional branch root
2498 # B N y additional branch root
2499 # C N y additional topo head
2499 # C N y additional topo head
2500 # H N n usual case
2500 # H N n usual case
2501 #
2501 #
2502 # B B y weird additional branch root
2502 # B B y weird additional branch root
2503 # C B y branch merge
2503 # C B y branch merge
2504 # H B n merge with named branch
2504 # H B n merge with named branch
2505 #
2505 #
2506 # C C y additional head from merge
2506 # C C y additional head from merge
2507 # C H n merge with a head
2507 # C H n merge with a head
2508 #
2508 #
2509 # H H n head merge: head count decreases
2509 # H H n head merge: head count decreases
2510
2510
2511 if not opts.get('close_branch'):
2511 if not opts.get('close_branch'):
2512 for r in parents:
2512 for r in parents:
2513 if r.closesbranch() and r.branch() == branch:
2513 if r.closesbranch() and r.branch() == branch:
2514 repo.ui.status(_('reopening closed branch head %d\n') % r)
2514 repo.ui.status(_('reopening closed branch head %d\n') % r)
2515
2515
2516 if repo.ui.debugflag:
2516 if repo.ui.debugflag:
2517 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2517 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2518 elif repo.ui.verbose:
2518 elif repo.ui.verbose:
2519 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2519 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2520
2520
2521 def revert(ui, repo, ctx, parents, *pats, **opts):
2521 def revert(ui, repo, ctx, parents, *pats, **opts):
2522 parent, p2 = parents
2522 parent, p2 = parents
2523 node = ctx.node()
2523 node = ctx.node()
2524
2524
2525 mf = ctx.manifest()
2525 mf = ctx.manifest()
2526 if node == p2:
2526 if node == p2:
2527 parent = p2
2527 parent = p2
2528 if node == parent:
2528 if node == parent:
2529 pmf = mf
2529 pmf = mf
2530 else:
2530 else:
2531 pmf = None
2531 pmf = None
2532
2532
2533 # need all matching names in dirstate and manifest of target rev,
2533 # need all matching names in dirstate and manifest of target rev,
2534 # so have to walk both. do not print errors if files exist in one
2534 # so have to walk both. do not print errors if files exist in one
2535 # but not other.
2535 # but not other.
2536
2536
2537 # `names` is a mapping for all elements in working copy and target revision
2537 # `names` is a mapping for all elements in working copy and target revision
2538 # The mapping is in the form:
2538 # The mapping is in the form:
2539 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2539 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2540 names = {}
2540 names = {}
2541
2541
2542 wlock = repo.wlock()
2542 wlock = repo.wlock()
2543 try:
2543 try:
2544 ## filling of the `names` mapping
2544 ## filling of the `names` mapping
2545 # walk dirstate to fill `names`
2545 # walk dirstate to fill `names`
2546
2546
2547 m = scmutil.match(repo[None], pats, opts)
2547 m = scmutil.match(repo[None], pats, opts)
2548 if not m.always() or node != parent:
2548 if not m.always() or node != parent:
2549 m.bad = lambda x, y: False
2549 m.bad = lambda x, y: False
2550 for abs in repo.walk(m):
2550 for abs in repo.walk(m):
2551 names[abs] = m.rel(abs), m.exact(abs)
2551 names[abs] = m.rel(abs), m.exact(abs)
2552
2552
2553 # walk target manifest to fill `names`
2553 # walk target manifest to fill `names`
2554
2554
2555 def badfn(path, msg):
2555 def badfn(path, msg):
2556 if path in names:
2556 if path in names:
2557 return
2557 return
2558 if path in ctx.substate:
2558 if path in ctx.substate:
2559 return
2559 return
2560 path_ = path + '/'
2560 path_ = path + '/'
2561 for f in names:
2561 for f in names:
2562 if f.startswith(path_):
2562 if f.startswith(path_):
2563 return
2563 return
2564 ui.warn("%s: %s\n" % (m.rel(path), msg))
2564 ui.warn("%s: %s\n" % (m.rel(path), msg))
2565
2565
2566 m = scmutil.match(ctx, pats, opts)
2566 m = scmutil.match(ctx, pats, opts)
2567 m.bad = badfn
2567 m.bad = badfn
2568 for abs in ctx.walk(m):
2568 for abs in ctx.walk(m):
2569 if abs not in names:
2569 if abs not in names:
2570 names[abs] = m.rel(abs), m.exact(abs)
2570 names[abs] = m.rel(abs), m.exact(abs)
2571
2571
2572 # Find status of all file in `names`.
2572 # Find status of all file in `names`.
2573 m = scmutil.matchfiles(repo, names)
2573 m = scmutil.matchfiles(repo, names)
2574
2574
2575 changes = repo.status(node1=node, match=m,
2575 changes = repo.status(node1=node, match=m,
2576 unknown=True, ignored=True, clean=True)
2576 unknown=True, ignored=True, clean=True)
2577 else:
2577 else:
2578 changes = repo.status(match=m)
2578 changes = repo.status(match=m)
2579 for kind in changes:
2579 for kind in changes:
2580 for abs in kind:
2580 for abs in kind:
2581 names[abs] = m.rel(abs), m.exact(abs)
2581 names[abs] = m.rel(abs), m.exact(abs)
2582
2582
2583 m = scmutil.matchfiles(repo, names)
2583 m = scmutil.matchfiles(repo, names)
2584
2584
2585 modified = set(changes.modified)
2585 modified = set(changes.modified)
2586 added = set(changes.added)
2586 added = set(changes.added)
2587 removed = set(changes.removed)
2587 removed = set(changes.removed)
2588 _deleted = set(changes.deleted)
2588 _deleted = set(changes.deleted)
2589 unknown = set(changes.unknown)
2589 unknown = set(changes.unknown)
2590 unknown.update(changes.ignored)
2590 unknown.update(changes.ignored)
2591 clean = set(changes.clean)
2591 clean = set(changes.clean)
2592 modadded = set()
2592 modadded = set()
2593
2593
2594 # split between files known in target manifest and the others
2594 # split between files known in target manifest and the others
2595 smf = set(mf)
2595 smf = set(mf)
2596
2596
2597 # determine the exact nature of the deleted changesets
2597 # determine the exact nature of the deleted changesets
2598 deladded = _deleted - smf
2598 deladded = _deleted - smf
2599 deleted = _deleted - deladded
2599 deleted = _deleted - deladded
2600
2600
2601 # We need to account for the state of file in the dirstate.
2601 # We need to account for the state of file in the dirstate.
2602 #
2602 #
2603 # Even, when we revert against something else than parent. This will
2603 # Even, when we revert against something else than parent. This will
2604 # slightly alter the behavior of revert (doing back up or not, delete
2604 # slightly alter the behavior of revert (doing back up or not, delete
2605 # or just forget etc).
2605 # or just forget etc).
2606 if parent == node:
2606 if parent == node:
2607 dsmodified = modified
2607 dsmodified = modified
2608 dsadded = added
2608 dsadded = added
2609 dsremoved = removed
2609 dsremoved = removed
2610 # store all local modifications, useful later for rename detection
2610 # store all local modifications, useful later for rename detection
2611 localchanges = dsmodified | dsadded
2611 localchanges = dsmodified | dsadded
2612 modified, added, removed = set(), set(), set()
2612 modified, added, removed = set(), set(), set()
2613 else:
2613 else:
2614 changes = repo.status(node1=parent, match=m)
2614 changes = repo.status(node1=parent, match=m)
2615 dsmodified = set(changes.modified)
2615 dsmodified = set(changes.modified)
2616 dsadded = set(changes.added)
2616 dsadded = set(changes.added)
2617 dsremoved = set(changes.removed)
2617 dsremoved = set(changes.removed)
2618 # store all local modifications, useful later for rename detection
2618 # store all local modifications, useful later for rename detection
2619 localchanges = dsmodified | dsadded
2619 localchanges = dsmodified | dsadded
2620
2620
2621 # only take into account for removes between wc and target
2621 # only take into account for removes between wc and target
2622 clean |= dsremoved - removed
2622 clean |= dsremoved - removed
2623 dsremoved &= removed
2623 dsremoved &= removed
2624 # distinct between dirstate remove and other
2624 # distinct between dirstate remove and other
2625 removed -= dsremoved
2625 removed -= dsremoved
2626
2626
2627 modadded = added & dsmodified
2627 modadded = added & dsmodified
2628 added -= modadded
2628 added -= modadded
2629
2629
2630 # tell newly modified apart.
2630 # tell newly modified apart.
2631 dsmodified &= modified
2631 dsmodified &= modified
2632 dsmodified |= modified & dsadded # dirstate added may needs backup
2632 dsmodified |= modified & dsadded # dirstate added may needs backup
2633 modified -= dsmodified
2633 modified -= dsmodified
2634
2634
2635 # We need to wait for some post-processing to update this set
2635 # We need to wait for some post-processing to update this set
2636 # before making the distinction. The dirstate will be used for
2636 # before making the distinction. The dirstate will be used for
2637 # that purpose.
2637 # that purpose.
2638 dsadded = added
2638 dsadded = added
2639
2639
2640 # in case of merge, files that are actually added can be reported as
2640 # in case of merge, files that are actually added can be reported as
2641 # modified, we need to post process the result
2641 # modified, we need to post process the result
2642 if p2 != nullid:
2642 if p2 != nullid:
2643 if pmf is None:
2643 if pmf is None:
2644 # only need parent manifest in the merge case,
2644 # only need parent manifest in the merge case,
2645 # so do not read by default
2645 # so do not read by default
2646 pmf = repo[parent].manifest()
2646 pmf = repo[parent].manifest()
2647 mergeadd = dsmodified - set(pmf)
2647 mergeadd = dsmodified - set(pmf)
2648 dsadded |= mergeadd
2648 dsadded |= mergeadd
2649 dsmodified -= mergeadd
2649 dsmodified -= mergeadd
2650
2650
2651 # if f is a rename, update `names` to also revert the source
2651 # if f is a rename, update `names` to also revert the source
2652 cwd = repo.getcwd()
2652 cwd = repo.getcwd()
2653 for f in localchanges:
2653 for f in localchanges:
2654 src = repo.dirstate.copied(f)
2654 src = repo.dirstate.copied(f)
2655 # XXX should we check for rename down to target node?
2655 # XXX should we check for rename down to target node?
2656 if src and src not in names and repo.dirstate[src] == 'r':
2656 if src and src not in names and repo.dirstate[src] == 'r':
2657 dsremoved.add(src)
2657 dsremoved.add(src)
2658 names[src] = (repo.pathto(src, cwd), True)
2658 names[src] = (repo.pathto(src, cwd), True)
2659
2659
2660 # distinguish between file to forget and the other
2660 # distinguish between file to forget and the other
2661 added = set()
2661 added = set()
2662 for abs in dsadded:
2662 for abs in dsadded:
2663 if repo.dirstate[abs] != 'a':
2663 if repo.dirstate[abs] != 'a':
2664 added.add(abs)
2664 added.add(abs)
2665 dsadded -= added
2665 dsadded -= added
2666
2666
2667 for abs in deladded:
2667 for abs in deladded:
2668 if repo.dirstate[abs] == 'a':
2668 if repo.dirstate[abs] == 'a':
2669 dsadded.add(abs)
2669 dsadded.add(abs)
2670 deladded -= dsadded
2670 deladded -= dsadded
2671
2671
2672 # For files marked as removed, we check if an unknown file is present at
2672 # For files marked as removed, we check if an unknown file is present at
2673 # the same path. If a such file exists it may need to be backed up.
2673 # the same path. If a such file exists it may need to be backed up.
2674 # Making the distinction at this stage helps have simpler backup
2674 # Making the distinction at this stage helps have simpler backup
2675 # logic.
2675 # logic.
2676 removunk = set()
2676 removunk = set()
2677 for abs in removed:
2677 for abs in removed:
2678 target = repo.wjoin(abs)
2678 target = repo.wjoin(abs)
2679 if os.path.lexists(target):
2679 if os.path.lexists(target):
2680 removunk.add(abs)
2680 removunk.add(abs)
2681 removed -= removunk
2681 removed -= removunk
2682
2682
2683 dsremovunk = set()
2683 dsremovunk = set()
2684 for abs in dsremoved:
2684 for abs in dsremoved:
2685 target = repo.wjoin(abs)
2685 target = repo.wjoin(abs)
2686 if os.path.lexists(target):
2686 if os.path.lexists(target):
2687 dsremovunk.add(abs)
2687 dsremovunk.add(abs)
2688 dsremoved -= dsremovunk
2688 dsremoved -= dsremovunk
2689
2689
2690 # action to be actually performed by revert
2690 # action to be actually performed by revert
2691 # (<list of file>, message>) tuple
2691 # (<list of file>, message>) tuple
2692 actions = {'revert': ([], _('reverting %s\n')),
2692 actions = {'revert': ([], _('reverting %s\n')),
2693 'add': ([], _('adding %s\n')),
2693 'add': ([], _('adding %s\n')),
2694 'remove': ([], _('removing %s\n')),
2694 'remove': ([], _('removing %s\n')),
2695 'drop': ([], _('removing %s\n')),
2695 'drop': ([], _('removing %s\n')),
2696 'forget': ([], _('forgetting %s\n')),
2696 'forget': ([], _('forgetting %s\n')),
2697 'undelete': ([], _('undeleting %s\n')),
2697 'undelete': ([], _('undeleting %s\n')),
2698 'noop': (None, _('no changes needed to %s\n')),
2698 'noop': (None, _('no changes needed to %s\n')),
2699 'unknown': (None, _('file not managed: %s\n')),
2699 'unknown': (None, _('file not managed: %s\n')),
2700 }
2700 }
2701
2701
2702 # "constant" that convey the backup strategy.
2702 # "constant" that convey the backup strategy.
2703 # All set to `discard` if `no-backup` is set do avoid checking
2703 # All set to `discard` if `no-backup` is set do avoid checking
2704 # no_backup lower in the code.
2704 # no_backup lower in the code.
2705 # These values are ordered for comparison purposes
2705 # These values are ordered for comparison purposes
2706 backup = 2 # unconditionally do backup
2706 backup = 2 # unconditionally do backup
2707 check = 1 # check if the existing file differs from target
2707 check = 1 # check if the existing file differs from target
2708 discard = 0 # never do backup
2708 discard = 0 # never do backup
2709 if opts.get('no_backup'):
2709 if opts.get('no_backup'):
2710 backup = check = discard
2710 backup = check = discard
2711
2711
2712 backupanddel = actions['remove']
2712 backupanddel = actions['remove']
2713 if not opts.get('no_backup'):
2713 if not opts.get('no_backup'):
2714 backupanddel = actions['drop']
2714 backupanddel = actions['drop']
2715
2715
2716 disptable = (
2716 disptable = (
2717 # dispatch table:
2717 # dispatch table:
2718 # file state
2718 # file state
2719 # action
2719 # action
2720 # make backup
2720 # make backup
2721
2721
2722 ## Sets that results that will change file on disk
2722 ## Sets that results that will change file on disk
2723 # Modified compared to target, no local change
2723 # Modified compared to target, no local change
2724 (modified, actions['revert'], discard),
2724 (modified, actions['revert'], discard),
2725 # Modified compared to target, but local file is deleted
2725 # Modified compared to target, but local file is deleted
2726 (deleted, actions['revert'], discard),
2726 (deleted, actions['revert'], discard),
2727 # Modified compared to target, local change
2727 # Modified compared to target, local change
2728 (dsmodified, actions['revert'], backup),
2728 (dsmodified, actions['revert'], backup),
2729 # Added since target
2729 # Added since target
2730 (added, actions['remove'], discard),
2730 (added, actions['remove'], discard),
2731 # Added in working directory
2731 # Added in working directory
2732 (dsadded, actions['forget'], discard),
2732 (dsadded, actions['forget'], discard),
2733 # Added since target, have local modification
2733 # Added since target, have local modification
2734 (modadded, backupanddel, backup),
2734 (modadded, backupanddel, backup),
2735 # Added since target but file is missing in working directory
2735 # Added since target but file is missing in working directory
2736 (deladded, actions['drop'], discard),
2736 (deladded, actions['drop'], discard),
2737 # Removed since target, before working copy parent
2737 # Removed since target, before working copy parent
2738 (removed, actions['add'], discard),
2738 (removed, actions['add'], discard),
2739 # Same as `removed` but an unknown file exists at the same path
2739 # Same as `removed` but an unknown file exists at the same path
2740 (removunk, actions['add'], check),
2740 (removunk, actions['add'], check),
2741 # Removed since targe, marked as such in working copy parent
2741 # Removed since targe, marked as such in working copy parent
2742 (dsremoved, actions['undelete'], discard),
2742 (dsremoved, actions['undelete'], discard),
2743 # Same as `dsremoved` but an unknown file exists at the same path
2743 # Same as `dsremoved` but an unknown file exists at the same path
2744 (dsremovunk, actions['undelete'], check),
2744 (dsremovunk, actions['undelete'], check),
2745 ## the following sets does not result in any file changes
2745 ## the following sets does not result in any file changes
2746 # File with no modification
2746 # File with no modification
2747 (clean, actions['noop'], discard),
2747 (clean, actions['noop'], discard),
2748 # Existing file, not tracked anywhere
2748 # Existing file, not tracked anywhere
2749 (unknown, actions['unknown'], discard),
2749 (unknown, actions['unknown'], discard),
2750 )
2750 )
2751
2751
2752 wctx = repo[None]
2752 wctx = repo[None]
2753 for abs, (rel, exact) in sorted(names.items()):
2753 for abs, (rel, exact) in sorted(names.items()):
2754 # target file to be touch on disk (relative to cwd)
2754 # target file to be touch on disk (relative to cwd)
2755 target = repo.wjoin(abs)
2755 target = repo.wjoin(abs)
2756 # search the entry in the dispatch table.
2756 # search the entry in the dispatch table.
2757 # if the file is in any of these sets, it was touched in the working
2757 # if the file is in any of these sets, it was touched in the working
2758 # directory parent and we are sure it needs to be reverted.
2758 # directory parent and we are sure it needs to be reverted.
2759 for table, (xlist, msg), dobackup in disptable:
2759 for table, (xlist, msg), dobackup in disptable:
2760 if abs not in table:
2760 if abs not in table:
2761 continue
2761 continue
2762 if xlist is not None:
2762 if xlist is not None:
2763 xlist.append(abs)
2763 xlist.append(abs)
2764 if dobackup and (backup <= dobackup
2764 if dobackup and (backup <= dobackup
2765 or wctx[abs].cmp(ctx[abs])):
2765 or wctx[abs].cmp(ctx[abs])):
2766 bakname = "%s.orig" % rel
2766 bakname = "%s.orig" % rel
2767 ui.note(_('saving current version of %s as %s\n') %
2767 ui.note(_('saving current version of %s as %s\n') %
2768 (rel, bakname))
2768 (rel, bakname))
2769 if not opts.get('dry_run'):
2769 if not opts.get('dry_run'):
2770 util.rename(target, bakname)
2770 util.rename(target, bakname)
2771 if ui.verbose or not exact:
2771 if ui.verbose or not exact:
2772 if not isinstance(msg, basestring):
2772 if not isinstance(msg, basestring):
2773 msg = msg(abs)
2773 msg = msg(abs)
2774 ui.status(msg % rel)
2774 ui.status(msg % rel)
2775 elif exact:
2775 elif exact:
2776 ui.warn(msg % rel)
2776 ui.warn(msg % rel)
2777 break
2777 break
2778
2778
2779
2779
2780 if not opts.get('dry_run'):
2780 if not opts.get('dry_run'):
2781 needdata = ('revert', 'add', 'undelete')
2781 needdata = ('revert', 'add', 'undelete')
2782 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
2782 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
2783
2783
2784 _performrevert(repo, parents, ctx, actions)
2784 _performrevert(repo, parents, ctx, actions)
2785
2785
2786 # get the list of subrepos that must be reverted
2786 # get the list of subrepos that must be reverted
2787 subrepomatch = scmutil.match(ctx, pats, opts)
2787 subrepomatch = scmutil.match(ctx, pats, opts)
2788 targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
2788 targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
2789
2789
2790 if targetsubs:
2790 if targetsubs:
2791 # Revert the subrepos on the revert list
2791 # Revert the subrepos on the revert list
2792 for sub in targetsubs:
2792 for sub in targetsubs:
2793 ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
2793 ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
2794 finally:
2794 finally:
2795 wlock.release()
2795 wlock.release()
2796
2796
2797 def _revertprefetch(repo, ctx, *files):
2797 def _revertprefetch(repo, ctx, *files):
2798 """Let extension changing the storage layer prefetch content"""
2798 """Let extension changing the storage layer prefetch content"""
2799 pass
2799 pass
2800
2800
2801 def _performrevert(repo, parents, ctx, actions):
2801 def _performrevert(repo, parents, ctx, actions):
2802 """function that actually perform all the actions computed for revert
2802 """function that actually perform all the actions computed for revert
2803
2803
2804 This is an independent function to let extension to plug in and react to
2804 This is an independent function to let extension to plug in and react to
2805 the imminent revert.
2805 the imminent revert.
2806
2806
2807 Make sure you have the working directory locked when calling this function.
2807 Make sure you have the working directory locked when calling this function.
2808 """
2808 """
2809 parent, p2 = parents
2809 parent, p2 = parents
2810 node = ctx.node()
2810 node = ctx.node()
2811 def checkout(f):
2811 def checkout(f):
2812 fc = ctx[f]
2812 fc = ctx[f]
2813 repo.wwrite(f, fc.data(), fc.flags())
2813 repo.wwrite(f, fc.data(), fc.flags())
2814
2814
2815 audit_path = pathutil.pathauditor(repo.root)
2815 audit_path = pathutil.pathauditor(repo.root)
2816 for f in actions['forget'][0]:
2816 for f in actions['forget'][0]:
2817 repo.dirstate.drop(f)
2817 repo.dirstate.drop(f)
2818 for f in actions['remove'][0]:
2818 for f in actions['remove'][0]:
2819 audit_path(f)
2819 audit_path(f)
2820 util.unlinkpath(repo.wjoin(f))
2820 util.unlinkpath(repo.wjoin(f))
2821 repo.dirstate.remove(f)
2821 repo.dirstate.remove(f)
2822 for f in actions['drop'][0]:
2822 for f in actions['drop'][0]:
2823 audit_path(f)
2823 audit_path(f)
2824 repo.dirstate.remove(f)
2824 repo.dirstate.remove(f)
2825
2825
2826 normal = None
2826 normal = None
2827 if node == parent:
2827 if node == parent:
2828 # We're reverting to our parent. If possible, we'd like status
2828 # We're reverting to our parent. If possible, we'd like status
2829 # to report the file as clean. We have to use normallookup for
2829 # to report the file as clean. We have to use normallookup for
2830 # merges to avoid losing information about merged/dirty files.
2830 # merges to avoid losing information about merged/dirty files.
2831 if p2 != nullid:
2831 if p2 != nullid:
2832 normal = repo.dirstate.normallookup
2832 normal = repo.dirstate.normallookup
2833 else:
2833 else:
2834 normal = repo.dirstate.normal
2834 normal = repo.dirstate.normal
2835 for f in actions['revert'][0]:
2835 for f in actions['revert'][0]:
2836 checkout(f)
2836 checkout(f)
2837 if normal:
2837 if normal:
2838 normal(f)
2838 normal(f)
2839
2839
2840 for f in actions['add'][0]:
2840 for f in actions['add'][0]:
2841 checkout(f)
2841 checkout(f)
2842 repo.dirstate.add(f)
2842 repo.dirstate.add(f)
2843
2843
2844 normal = repo.dirstate.normallookup
2844 normal = repo.dirstate.normallookup
2845 if node == parent and p2 == nullid:
2845 if node == parent and p2 == nullid:
2846 normal = repo.dirstate.normal
2846 normal = repo.dirstate.normal
2847 for f in actions['undelete'][0]:
2847 for f in actions['undelete'][0]:
2848 checkout(f)
2848 checkout(f)
2849 normal(f)
2849 normal(f)
2850
2850
2851 copied = copies.pathcopies(repo[parent], ctx)
2851 copied = copies.pathcopies(repo[parent], ctx)
2852
2852
2853 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2853 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2854 if f in copied:
2854 if f in copied:
2855 repo.dirstate.copy(copied[f], f)
2855 repo.dirstate.copy(copied[f], f)
2856
2856
2857 def command(table):
2857 def command(table):
2858 """Returns a function object to be used as a decorator for making commands.
2858 """Returns a function object to be used as a decorator for making commands.
2859
2859
2860 This function receives a command table as its argument. The table should
2860 This function receives a command table as its argument. The table should
2861 be a dict.
2861 be a dict.
2862
2862
2863 The returned function can be used as a decorator for adding commands
2863 The returned function can be used as a decorator for adding commands
2864 to that command table. This function accepts multiple arguments to define
2864 to that command table. This function accepts multiple arguments to define
2865 a command.
2865 a command.
2866
2866
2867 The first argument is the command name.
2867 The first argument is the command name.
2868
2868
2869 The options argument is an iterable of tuples defining command arguments.
2869 The options argument is an iterable of tuples defining command arguments.
2870 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2870 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2871
2871
2872 The synopsis argument defines a short, one line summary of how to use the
2872 The synopsis argument defines a short, one line summary of how to use the
2873 command. This shows up in the help output.
2873 command. This shows up in the help output.
2874
2874
2875 The norepo argument defines whether the command does not require a
2875 The norepo argument defines whether the command does not require a
2876 local repository. Most commands operate against a repository, thus the
2876 local repository. Most commands operate against a repository, thus the
2877 default is False.
2877 default is False.
2878
2878
2879 The optionalrepo argument defines whether the command optionally requires
2879 The optionalrepo argument defines whether the command optionally requires
2880 a local repository.
2880 a local repository.
2881
2881
2882 The inferrepo argument defines whether to try to find a repository from the
2882 The inferrepo argument defines whether to try to find a repository from the
2883 command line arguments. If True, arguments will be examined for potential
2883 command line arguments. If True, arguments will be examined for potential
2884 repository locations. See ``findrepo()``. If a repository is found, it
2884 repository locations. See ``findrepo()``. If a repository is found, it
2885 will be used.
2885 will be used.
2886 """
2886 """
2887 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2887 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2888 inferrepo=False):
2888 inferrepo=False):
2889 def decorator(func):
2889 def decorator(func):
2890 if synopsis:
2890 if synopsis:
2891 table[name] = func, list(options), synopsis
2891 table[name] = func, list(options), synopsis
2892 else:
2892 else:
2893 table[name] = func, list(options)
2893 table[name] = func, list(options)
2894
2894
2895 if norepo:
2895 if norepo:
2896 # Avoid import cycle.
2896 # Avoid import cycle.
2897 import commands
2897 import commands
2898 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2898 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2899
2899
2900 if optionalrepo:
2900 if optionalrepo:
2901 import commands
2901 import commands
2902 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2902 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2903
2903
2904 if inferrepo:
2904 if inferrepo:
2905 import commands
2905 import commands
2906 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2906 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2907
2907
2908 return func
2908 return func
2909 return decorator
2909 return decorator
2910
2910
2911 return cmd
2911 return cmd
2912
2912
2913 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2913 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2914 # commands.outgoing. "missing" is "missing" of the result of
2914 # commands.outgoing. "missing" is "missing" of the result of
2915 # "findcommonoutgoing()"
2915 # "findcommonoutgoing()"
2916 outgoinghooks = util.hooks()
2916 outgoinghooks = util.hooks()
2917
2917
2918 # a list of (ui, repo) functions called by commands.summary
2918 # a list of (ui, repo) functions called by commands.summary
2919 summaryhooks = util.hooks()
2919 summaryhooks = util.hooks()
2920
2920
2921 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2921 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2922 #
2922 #
2923 # functions should return tuple of booleans below, if 'changes' is None:
2923 # functions should return tuple of booleans below, if 'changes' is None:
2924 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2924 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2925 #
2925 #
2926 # otherwise, 'changes' is a tuple of tuples below:
2926 # otherwise, 'changes' is a tuple of tuples below:
2927 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2927 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2928 # - (desturl, destbranch, destpeer, outgoing)
2928 # - (desturl, destbranch, destpeer, outgoing)
2929 summaryremotehooks = util.hooks()
2929 summaryremotehooks = util.hooks()
2930
2930
2931 # A list of state files kept by multistep operations like graft.
2931 # A list of state files kept by multistep operations like graft.
2932 # Since graft cannot be aborted, it is considered 'clearable' by update.
2932 # Since graft cannot be aborted, it is considered 'clearable' by update.
2933 # note: bisect is intentionally excluded
2933 # note: bisect is intentionally excluded
2934 # (state file, clearable, allowcommit, error, hint)
2934 # (state file, clearable, allowcommit, error, hint)
2935 unfinishedstates = [
2935 unfinishedstates = [
2936 ('graftstate', True, False, _('graft in progress'),
2936 ('graftstate', True, False, _('graft in progress'),
2937 _("use 'hg graft --continue' or 'hg update' to abort")),
2937 _("use 'hg graft --continue' or 'hg update' to abort")),
2938 ('updatestate', True, False, _('last update was interrupted'),
2938 ('updatestate', True, False, _('last update was interrupted'),
2939 _("use 'hg update' to get a consistent checkout"))
2939 _("use 'hg update' to get a consistent checkout"))
2940 ]
2940 ]
2941
2941
2942 def checkunfinished(repo, commit=False):
2942 def checkunfinished(repo, commit=False):
2943 '''Look for an unfinished multistep operation, like graft, and abort
2943 '''Look for an unfinished multistep operation, like graft, and abort
2944 if found. It's probably good to check this right before
2944 if found. It's probably good to check this right before
2945 bailifchanged().
2945 bailifchanged().
2946 '''
2946 '''
2947 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2947 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2948 if commit and allowcommit:
2948 if commit and allowcommit:
2949 continue
2949 continue
2950 if repo.vfs.exists(f):
2950 if repo.vfs.exists(f):
2951 raise util.Abort(msg, hint=hint)
2951 raise util.Abort(msg, hint=hint)
2952
2952
2953 def clearunfinished(repo):
2953 def clearunfinished(repo):
2954 '''Check for unfinished operations (as above), and clear the ones
2954 '''Check for unfinished operations (as above), and clear the ones
2955 that are clearable.
2955 that are clearable.
2956 '''
2956 '''
2957 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2957 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2958 if not clearable and repo.vfs.exists(f):
2958 if not clearable and repo.vfs.exists(f):
2959 raise util.Abort(msg, hint=hint)
2959 raise util.Abort(msg, hint=hint)
2960 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2960 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2961 if clearable and repo.vfs.exists(f):
2961 if clearable and repo.vfs.exists(f):
2962 util.unlink(repo.join(f))
2962 util.unlink(repo.join(f))
@@ -1,1706 +1,1708 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 import copy
8 import copy
9 import errno, os, re, shutil, posixpath, sys
9 import errno, os, re, shutil, posixpath, sys
10 import xml.dom.minidom
10 import xml.dom.minidom
11 import stat, subprocess, tarfile
11 import stat, subprocess, tarfile
12 from i18n import _
12 from i18n import _
13 import config, util, node, error, cmdutil, scmutil, match as matchmod
13 import config, util, node, error, cmdutil, scmutil, match as matchmod
14 import phases
14 import phases
15 import pathutil
15 import pathutil
16 import exchange
16 import exchange
17 hg = None
17 hg = None
18 propertycache = util.propertycache
18 propertycache = util.propertycache
19
19
20 nullstate = ('', '', 'empty')
20 nullstate = ('', '', 'empty')
21
21
22 def _expandedabspath(path):
22 def _expandedabspath(path):
23 '''
23 '''
24 get a path or url and if it is a path expand it and return an absolute path
24 get a path or url and if it is a path expand it and return an absolute path
25 '''
25 '''
26 expandedpath = util.urllocalpath(util.expandpath(path))
26 expandedpath = util.urllocalpath(util.expandpath(path))
27 u = util.url(expandedpath)
27 u = util.url(expandedpath)
28 if not u.scheme:
28 if not u.scheme:
29 path = util.normpath(os.path.abspath(u.path))
29 path = util.normpath(os.path.abspath(u.path))
30 return path
30 return path
31
31
32 def _getstorehashcachename(remotepath):
32 def _getstorehashcachename(remotepath):
33 '''get a unique filename for the store hash cache of a remote repository'''
33 '''get a unique filename for the store hash cache of a remote repository'''
34 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
34 return util.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
35
35
36 class SubrepoAbort(error.Abort):
36 class SubrepoAbort(error.Abort):
37 """Exception class used to avoid handling a subrepo error more than once"""
37 """Exception class used to avoid handling a subrepo error more than once"""
38 def __init__(self, *args, **kw):
38 def __init__(self, *args, **kw):
39 error.Abort.__init__(self, *args, **kw)
39 error.Abort.__init__(self, *args, **kw)
40 self.subrepo = kw.get('subrepo')
40 self.subrepo = kw.get('subrepo')
41 self.cause = kw.get('cause')
41 self.cause = kw.get('cause')
42
42
43 def annotatesubrepoerror(func):
43 def annotatesubrepoerror(func):
44 def decoratedmethod(self, *args, **kargs):
44 def decoratedmethod(self, *args, **kargs):
45 try:
45 try:
46 res = func(self, *args, **kargs)
46 res = func(self, *args, **kargs)
47 except SubrepoAbort, ex:
47 except SubrepoAbort, ex:
48 # This exception has already been handled
48 # This exception has already been handled
49 raise ex
49 raise ex
50 except error.Abort, ex:
50 except error.Abort, ex:
51 subrepo = subrelpath(self)
51 subrepo = subrelpath(self)
52 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
52 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
53 # avoid handling this exception by raising a SubrepoAbort exception
53 # avoid handling this exception by raising a SubrepoAbort exception
54 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
54 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
55 cause=sys.exc_info())
55 cause=sys.exc_info())
56 return res
56 return res
57 return decoratedmethod
57 return decoratedmethod
58
58
59 def state(ctx, ui):
59 def state(ctx, ui):
60 """return a state dict, mapping subrepo paths configured in .hgsub
60 """return a state dict, mapping subrepo paths configured in .hgsub
61 to tuple: (source from .hgsub, revision from .hgsubstate, kind
61 to tuple: (source from .hgsub, revision from .hgsubstate, kind
62 (key in types dict))
62 (key in types dict))
63 """
63 """
64 p = config.config()
64 p = config.config()
65 def read(f, sections=None, remap=None):
65 def read(f, sections=None, remap=None):
66 if f in ctx:
66 if f in ctx:
67 try:
67 try:
68 data = ctx[f].data()
68 data = ctx[f].data()
69 except IOError, err:
69 except IOError, err:
70 if err.errno != errno.ENOENT:
70 if err.errno != errno.ENOENT:
71 raise
71 raise
72 # handle missing subrepo spec files as removed
72 # handle missing subrepo spec files as removed
73 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
73 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
74 return
74 return
75 p.parse(f, data, sections, remap, read)
75 p.parse(f, data, sections, remap, read)
76 else:
76 else:
77 raise util.Abort(_("subrepo spec file %s not found") % f)
77 raise util.Abort(_("subrepo spec file %s not found") % f)
78
78
79 if '.hgsub' in ctx:
79 if '.hgsub' in ctx:
80 read('.hgsub')
80 read('.hgsub')
81
81
82 for path, src in ui.configitems('subpaths'):
82 for path, src in ui.configitems('subpaths'):
83 p.set('subpaths', path, src, ui.configsource('subpaths', path))
83 p.set('subpaths', path, src, ui.configsource('subpaths', path))
84
84
85 rev = {}
85 rev = {}
86 if '.hgsubstate' in ctx:
86 if '.hgsubstate' in ctx:
87 try:
87 try:
88 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
88 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
89 l = l.lstrip()
89 l = l.lstrip()
90 if not l:
90 if not l:
91 continue
91 continue
92 try:
92 try:
93 revision, path = l.split(" ", 1)
93 revision, path = l.split(" ", 1)
94 except ValueError:
94 except ValueError:
95 raise util.Abort(_("invalid subrepository revision "
95 raise util.Abort(_("invalid subrepository revision "
96 "specifier in .hgsubstate line %d")
96 "specifier in .hgsubstate line %d")
97 % (i + 1))
97 % (i + 1))
98 rev[path] = revision
98 rev[path] = revision
99 except IOError, err:
99 except IOError, err:
100 if err.errno != errno.ENOENT:
100 if err.errno != errno.ENOENT:
101 raise
101 raise
102
102
103 def remap(src):
103 def remap(src):
104 for pattern, repl in p.items('subpaths'):
104 for pattern, repl in p.items('subpaths'):
105 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
105 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
106 # does a string decode.
106 # does a string decode.
107 repl = repl.encode('string-escape')
107 repl = repl.encode('string-escape')
108 # However, we still want to allow back references to go
108 # However, we still want to allow back references to go
109 # through unharmed, so we turn r'\\1' into r'\1'. Again,
109 # through unharmed, so we turn r'\\1' into r'\1'. Again,
110 # extra escapes are needed because re.sub string decodes.
110 # extra escapes are needed because re.sub string decodes.
111 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
111 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
112 try:
112 try:
113 src = re.sub(pattern, repl, src, 1)
113 src = re.sub(pattern, repl, src, 1)
114 except re.error, e:
114 except re.error, e:
115 raise util.Abort(_("bad subrepository pattern in %s: %s")
115 raise util.Abort(_("bad subrepository pattern in %s: %s")
116 % (p.source('subpaths', pattern), e))
116 % (p.source('subpaths', pattern), e))
117 return src
117 return src
118
118
119 state = {}
119 state = {}
120 for path, src in p[''].items():
120 for path, src in p[''].items():
121 kind = 'hg'
121 kind = 'hg'
122 if src.startswith('['):
122 if src.startswith('['):
123 if ']' not in src:
123 if ']' not in src:
124 raise util.Abort(_('missing ] in subrepo source'))
124 raise util.Abort(_('missing ] in subrepo source'))
125 kind, src = src.split(']', 1)
125 kind, src = src.split(']', 1)
126 kind = kind[1:]
126 kind = kind[1:]
127 src = src.lstrip() # strip any extra whitespace after ']'
127 src = src.lstrip() # strip any extra whitespace after ']'
128
128
129 if not util.url(src).isabs():
129 if not util.url(src).isabs():
130 parent = _abssource(ctx._repo, abort=False)
130 parent = _abssource(ctx._repo, abort=False)
131 if parent:
131 if parent:
132 parent = util.url(parent)
132 parent = util.url(parent)
133 parent.path = posixpath.join(parent.path or '', src)
133 parent.path = posixpath.join(parent.path or '', src)
134 parent.path = posixpath.normpath(parent.path)
134 parent.path = posixpath.normpath(parent.path)
135 joined = str(parent)
135 joined = str(parent)
136 # Remap the full joined path and use it if it changes,
136 # Remap the full joined path and use it if it changes,
137 # else remap the original source.
137 # else remap the original source.
138 remapped = remap(joined)
138 remapped = remap(joined)
139 if remapped == joined:
139 if remapped == joined:
140 src = remap(src)
140 src = remap(src)
141 else:
141 else:
142 src = remapped
142 src = remapped
143
143
144 src = remap(src)
144 src = remap(src)
145 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
145 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
146
146
147 return state
147 return state
148
148
149 def writestate(repo, state):
149 def writestate(repo, state):
150 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
150 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
151 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
151 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
152 repo.wwrite('.hgsubstate', ''.join(lines), '')
152 repo.wwrite('.hgsubstate', ''.join(lines), '')
153
153
154 def submerge(repo, wctx, mctx, actx, overwrite):
154 def submerge(repo, wctx, mctx, actx, overwrite):
155 """delegated from merge.applyupdates: merging of .hgsubstate file
155 """delegated from merge.applyupdates: merging of .hgsubstate file
156 in working context, merging context and ancestor context"""
156 in working context, merging context and ancestor context"""
157 if mctx == actx: # backwards?
157 if mctx == actx: # backwards?
158 actx = wctx.p1()
158 actx = wctx.p1()
159 s1 = wctx.substate
159 s1 = wctx.substate
160 s2 = mctx.substate
160 s2 = mctx.substate
161 sa = actx.substate
161 sa = actx.substate
162 sm = {}
162 sm = {}
163
163
164 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
164 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
165
165
166 def debug(s, msg, r=""):
166 def debug(s, msg, r=""):
167 if r:
167 if r:
168 r = "%s:%s:%s" % r
168 r = "%s:%s:%s" % r
169 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
169 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
170
170
171 for s, l in sorted(s1.iteritems()):
171 for s, l in sorted(s1.iteritems()):
172 a = sa.get(s, nullstate)
172 a = sa.get(s, nullstate)
173 ld = l # local state with possible dirty flag for compares
173 ld = l # local state with possible dirty flag for compares
174 if wctx.sub(s).dirty():
174 if wctx.sub(s).dirty():
175 ld = (l[0], l[1] + "+")
175 ld = (l[0], l[1] + "+")
176 if wctx == actx: # overwrite
176 if wctx == actx: # overwrite
177 a = ld
177 a = ld
178
178
179 if s in s2:
179 if s in s2:
180 r = s2[s]
180 r = s2[s]
181 if ld == r or r == a: # no change or local is newer
181 if ld == r or r == a: # no change or local is newer
182 sm[s] = l
182 sm[s] = l
183 continue
183 continue
184 elif ld == a: # other side changed
184 elif ld == a: # other side changed
185 debug(s, "other changed, get", r)
185 debug(s, "other changed, get", r)
186 wctx.sub(s).get(r, overwrite)
186 wctx.sub(s).get(r, overwrite)
187 sm[s] = r
187 sm[s] = r
188 elif ld[0] != r[0]: # sources differ
188 elif ld[0] != r[0]: # sources differ
189 if repo.ui.promptchoice(
189 if repo.ui.promptchoice(
190 _(' subrepository sources for %s differ\n'
190 _(' subrepository sources for %s differ\n'
191 'use (l)ocal source (%s) or (r)emote source (%s)?'
191 'use (l)ocal source (%s) or (r)emote source (%s)?'
192 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
192 '$$ &Local $$ &Remote') % (s, l[0], r[0]), 0):
193 debug(s, "prompt changed, get", r)
193 debug(s, "prompt changed, get", r)
194 wctx.sub(s).get(r, overwrite)
194 wctx.sub(s).get(r, overwrite)
195 sm[s] = r
195 sm[s] = r
196 elif ld[1] == a[1]: # local side is unchanged
196 elif ld[1] == a[1]: # local side is unchanged
197 debug(s, "other side changed, get", r)
197 debug(s, "other side changed, get", r)
198 wctx.sub(s).get(r, overwrite)
198 wctx.sub(s).get(r, overwrite)
199 sm[s] = r
199 sm[s] = r
200 else:
200 else:
201 debug(s, "both sides changed")
201 debug(s, "both sides changed")
202 srepo = wctx.sub(s)
202 srepo = wctx.sub(s)
203 option = repo.ui.promptchoice(
203 option = repo.ui.promptchoice(
204 _(' subrepository %s diverged (local revision: %s, '
204 _(' subrepository %s diverged (local revision: %s, '
205 'remote revision: %s)\n'
205 'remote revision: %s)\n'
206 '(M)erge, keep (l)ocal or keep (r)emote?'
206 '(M)erge, keep (l)ocal or keep (r)emote?'
207 '$$ &Merge $$ &Local $$ &Remote')
207 '$$ &Merge $$ &Local $$ &Remote')
208 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
208 % (s, srepo.shortid(l[1]), srepo.shortid(r[1])), 0)
209 if option == 0:
209 if option == 0:
210 wctx.sub(s).merge(r)
210 wctx.sub(s).merge(r)
211 sm[s] = l
211 sm[s] = l
212 debug(s, "merge with", r)
212 debug(s, "merge with", r)
213 elif option == 1:
213 elif option == 1:
214 sm[s] = l
214 sm[s] = l
215 debug(s, "keep local subrepo revision", l)
215 debug(s, "keep local subrepo revision", l)
216 else:
216 else:
217 wctx.sub(s).get(r, overwrite)
217 wctx.sub(s).get(r, overwrite)
218 sm[s] = r
218 sm[s] = r
219 debug(s, "get remote subrepo revision", r)
219 debug(s, "get remote subrepo revision", r)
220 elif ld == a: # remote removed, local unchanged
220 elif ld == a: # remote removed, local unchanged
221 debug(s, "remote removed, remove")
221 debug(s, "remote removed, remove")
222 wctx.sub(s).remove()
222 wctx.sub(s).remove()
223 elif a == nullstate: # not present in remote or ancestor
223 elif a == nullstate: # not present in remote or ancestor
224 debug(s, "local added, keep")
224 debug(s, "local added, keep")
225 sm[s] = l
225 sm[s] = l
226 continue
226 continue
227 else:
227 else:
228 if repo.ui.promptchoice(
228 if repo.ui.promptchoice(
229 _(' local changed subrepository %s which remote removed\n'
229 _(' local changed subrepository %s which remote removed\n'
230 'use (c)hanged version or (d)elete?'
230 'use (c)hanged version or (d)elete?'
231 '$$ &Changed $$ &Delete') % s, 0):
231 '$$ &Changed $$ &Delete') % s, 0):
232 debug(s, "prompt remove")
232 debug(s, "prompt remove")
233 wctx.sub(s).remove()
233 wctx.sub(s).remove()
234
234
235 for s, r in sorted(s2.items()):
235 for s, r in sorted(s2.items()):
236 if s in s1:
236 if s in s1:
237 continue
237 continue
238 elif s not in sa:
238 elif s not in sa:
239 debug(s, "remote added, get", r)
239 debug(s, "remote added, get", r)
240 mctx.sub(s).get(r)
240 mctx.sub(s).get(r)
241 sm[s] = r
241 sm[s] = r
242 elif r != sa[s]:
242 elif r != sa[s]:
243 if repo.ui.promptchoice(
243 if repo.ui.promptchoice(
244 _(' remote changed subrepository %s which local removed\n'
244 _(' remote changed subrepository %s which local removed\n'
245 'use (c)hanged version or (d)elete?'
245 'use (c)hanged version or (d)elete?'
246 '$$ &Changed $$ &Delete') % s, 0) == 0:
246 '$$ &Changed $$ &Delete') % s, 0) == 0:
247 debug(s, "prompt recreate", r)
247 debug(s, "prompt recreate", r)
248 wctx.sub(s).get(r)
248 wctx.sub(s).get(r)
249 sm[s] = r
249 sm[s] = r
250
250
251 # record merged .hgsubstate
251 # record merged .hgsubstate
252 writestate(repo, sm)
252 writestate(repo, sm)
253 return sm
253 return sm
254
254
255 def _updateprompt(ui, sub, dirty, local, remote):
255 def _updateprompt(ui, sub, dirty, local, remote):
256 if dirty:
256 if dirty:
257 msg = (_(' subrepository sources for %s differ\n'
257 msg = (_(' subrepository sources for %s differ\n'
258 'use (l)ocal source (%s) or (r)emote source (%s)?'
258 'use (l)ocal source (%s) or (r)emote source (%s)?'
259 '$$ &Local $$ &Remote')
259 '$$ &Local $$ &Remote')
260 % (subrelpath(sub), local, remote))
260 % (subrelpath(sub), local, remote))
261 else:
261 else:
262 msg = (_(' subrepository sources for %s differ (in checked out '
262 msg = (_(' subrepository sources for %s differ (in checked out '
263 'version)\n'
263 'version)\n'
264 'use (l)ocal source (%s) or (r)emote source (%s)?'
264 'use (l)ocal source (%s) or (r)emote source (%s)?'
265 '$$ &Local $$ &Remote')
265 '$$ &Local $$ &Remote')
266 % (subrelpath(sub), local, remote))
266 % (subrelpath(sub), local, remote))
267 return ui.promptchoice(msg, 0)
267 return ui.promptchoice(msg, 0)
268
268
269 def reporelpath(repo):
269 def reporelpath(repo):
270 """return path to this (sub)repo as seen from outermost repo"""
270 """return path to this (sub)repo as seen from outermost repo"""
271 parent = repo
271 parent = repo
272 while util.safehasattr(parent, '_subparent'):
272 while util.safehasattr(parent, '_subparent'):
273 parent = parent._subparent
273 parent = parent._subparent
274 return repo.root[len(pathutil.normasprefix(parent.root)):]
274 return repo.root[len(pathutil.normasprefix(parent.root)):]
275
275
276 def subrelpath(sub):
276 def subrelpath(sub):
277 """return path to this subrepo as seen from outermost repo"""
277 """return path to this subrepo as seen from outermost repo"""
278 if util.safehasattr(sub, '_relpath'):
278 if util.safehasattr(sub, '_relpath'):
279 return sub._relpath
279 return sub._relpath
280 if not util.safehasattr(sub, '_repo'):
280 if not util.safehasattr(sub, '_repo'):
281 return sub._path
281 return sub._path
282 return reporelpath(sub._repo)
282 return reporelpath(sub._repo)
283
283
284 def _abssource(repo, push=False, abort=True):
284 def _abssource(repo, push=False, abort=True):
285 """return pull/push path of repo - either based on parent repo .hgsub info
285 """return pull/push path of repo - either based on parent repo .hgsub info
286 or on the top repo config. Abort or return None if no source found."""
286 or on the top repo config. Abort or return None if no source found."""
287 if util.safehasattr(repo, '_subparent'):
287 if util.safehasattr(repo, '_subparent'):
288 source = util.url(repo._subsource)
288 source = util.url(repo._subsource)
289 if source.isabs():
289 if source.isabs():
290 return str(source)
290 return str(source)
291 source.path = posixpath.normpath(source.path)
291 source.path = posixpath.normpath(source.path)
292 parent = _abssource(repo._subparent, push, abort=False)
292 parent = _abssource(repo._subparent, push, abort=False)
293 if parent:
293 if parent:
294 parent = util.url(util.pconvert(parent))
294 parent = util.url(util.pconvert(parent))
295 parent.path = posixpath.join(parent.path or '', source.path)
295 parent.path = posixpath.join(parent.path or '', source.path)
296 parent.path = posixpath.normpath(parent.path)
296 parent.path = posixpath.normpath(parent.path)
297 return str(parent)
297 return str(parent)
298 else: # recursion reached top repo
298 else: # recursion reached top repo
299 if util.safehasattr(repo, '_subtoppath'):
299 if util.safehasattr(repo, '_subtoppath'):
300 return repo._subtoppath
300 return repo._subtoppath
301 if push and repo.ui.config('paths', 'default-push'):
301 if push and repo.ui.config('paths', 'default-push'):
302 return repo.ui.config('paths', 'default-push')
302 return repo.ui.config('paths', 'default-push')
303 if repo.ui.config('paths', 'default'):
303 if repo.ui.config('paths', 'default'):
304 return repo.ui.config('paths', 'default')
304 return repo.ui.config('paths', 'default')
305 if repo.shared():
305 if repo.shared():
306 # chop off the .hg component to get the default path form
306 # chop off the .hg component to get the default path form
307 return os.path.dirname(repo.sharedpath)
307 return os.path.dirname(repo.sharedpath)
308 if abort:
308 if abort:
309 raise util.Abort(_("default path for subrepository not found"))
309 raise util.Abort(_("default path for subrepository not found"))
310
310
311 def _sanitize(ui, path, ignore):
311 def _sanitize(ui, path, ignore):
312 for dirname, dirs, names in os.walk(path):
312 for dirname, dirs, names in os.walk(path):
313 for i, d in enumerate(dirs):
313 for i, d in enumerate(dirs):
314 if d.lower() == ignore:
314 if d.lower() == ignore:
315 del dirs[i]
315 del dirs[i]
316 break
316 break
317 if os.path.basename(dirname).lower() != '.hg':
317 if os.path.basename(dirname).lower() != '.hg':
318 continue
318 continue
319 for f in names:
319 for f in names:
320 if f.lower() == 'hgrc':
320 if f.lower() == 'hgrc':
321 ui.warn(_("warning: removing potentially hostile 'hgrc' "
321 ui.warn(_("warning: removing potentially hostile 'hgrc' "
322 "in '%s'\n") % dirname)
322 "in '%s'\n") % dirname)
323 os.unlink(os.path.join(dirname, f))
323 os.unlink(os.path.join(dirname, f))
324
324
325 def subrepo(ctx, path):
325 def subrepo(ctx, path):
326 """return instance of the right subrepo class for subrepo in path"""
326 """return instance of the right subrepo class for subrepo in path"""
327 # subrepo inherently violates our import layering rules
327 # subrepo inherently violates our import layering rules
328 # because it wants to make repo objects from deep inside the stack
328 # because it wants to make repo objects from deep inside the stack
329 # so we manually delay the circular imports to not break
329 # so we manually delay the circular imports to not break
330 # scripts that don't use our demand-loading
330 # scripts that don't use our demand-loading
331 global hg
331 global hg
332 import hg as h
332 import hg as h
333 hg = h
333 hg = h
334
334
335 pathutil.pathauditor(ctx._repo.root)(path)
335 pathutil.pathauditor(ctx._repo.root)(path)
336 state = ctx.substate[path]
336 state = ctx.substate[path]
337 if state[2] not in types:
337 if state[2] not in types:
338 raise util.Abort(_('unknown subrepo type %s') % state[2])
338 raise util.Abort(_('unknown subrepo type %s') % state[2])
339 return types[state[2]](ctx, path, state[:2])
339 return types[state[2]](ctx, path, state[:2])
340
340
341 def newcommitphase(ui, ctx):
341 def newcommitphase(ui, ctx):
342 commitphase = phases.newcommitphase(ui)
342 commitphase = phases.newcommitphase(ui)
343 substate = getattr(ctx, "substate", None)
343 substate = getattr(ctx, "substate", None)
344 if not substate:
344 if not substate:
345 return commitphase
345 return commitphase
346 check = ui.config('phases', 'checksubrepos', 'follow')
346 check = ui.config('phases', 'checksubrepos', 'follow')
347 if check not in ('ignore', 'follow', 'abort'):
347 if check not in ('ignore', 'follow', 'abort'):
348 raise util.Abort(_('invalid phases.checksubrepos configuration: %s')
348 raise util.Abort(_('invalid phases.checksubrepos configuration: %s')
349 % (check))
349 % (check))
350 if check == 'ignore':
350 if check == 'ignore':
351 return commitphase
351 return commitphase
352 maxphase = phases.public
352 maxphase = phases.public
353 maxsub = None
353 maxsub = None
354 for s in sorted(substate):
354 for s in sorted(substate):
355 sub = ctx.sub(s)
355 sub = ctx.sub(s)
356 subphase = sub.phase(substate[s][1])
356 subphase = sub.phase(substate[s][1])
357 if maxphase < subphase:
357 if maxphase < subphase:
358 maxphase = subphase
358 maxphase = subphase
359 maxsub = s
359 maxsub = s
360 if commitphase < maxphase:
360 if commitphase < maxphase:
361 if check == 'abort':
361 if check == 'abort':
362 raise util.Abort(_("can't commit in %s phase"
362 raise util.Abort(_("can't commit in %s phase"
363 " conflicting %s from subrepository %s") %
363 " conflicting %s from subrepository %s") %
364 (phases.phasenames[commitphase],
364 (phases.phasenames[commitphase],
365 phases.phasenames[maxphase], maxsub))
365 phases.phasenames[maxphase], maxsub))
366 ui.warn(_("warning: changes are committed in"
366 ui.warn(_("warning: changes are committed in"
367 " %s phase from subrepository %s\n") %
367 " %s phase from subrepository %s\n") %
368 (phases.phasenames[maxphase], maxsub))
368 (phases.phasenames[maxphase], maxsub))
369 return maxphase
369 return maxphase
370 return commitphase
370 return commitphase
371
371
372 # subrepo classes need to implement the following abstract class:
372 # subrepo classes need to implement the following abstract class:
373
373
374 class abstractsubrepo(object):
374 class abstractsubrepo(object):
375
375
376 def __init__(self, ui):
376 def __init__(self, ui):
377 self.ui = ui
377 self.ui = ui
378
378
379 def storeclean(self, path):
379 def storeclean(self, path):
380 """
380 """
381 returns true if the repository has not changed since it was last
381 returns true if the repository has not changed since it was last
382 cloned from or pushed to a given repository.
382 cloned from or pushed to a given repository.
383 """
383 """
384 return False
384 return False
385
385
386 def dirty(self, ignoreupdate=False):
386 def dirty(self, ignoreupdate=False):
387 """returns true if the dirstate of the subrepo is dirty or does not
387 """returns true if the dirstate of the subrepo is dirty or does not
388 match current stored state. If ignoreupdate is true, only check
388 match current stored state. If ignoreupdate is true, only check
389 whether the subrepo has uncommitted changes in its dirstate.
389 whether the subrepo has uncommitted changes in its dirstate.
390 """
390 """
391 raise NotImplementedError
391 raise NotImplementedError
392
392
393 def basestate(self):
393 def basestate(self):
394 """current working directory base state, disregarding .hgsubstate
394 """current working directory base state, disregarding .hgsubstate
395 state and working directory modifications"""
395 state and working directory modifications"""
396 raise NotImplementedError
396 raise NotImplementedError
397
397
398 def checknested(self, path):
398 def checknested(self, path):
399 """check if path is a subrepository within this repository"""
399 """check if path is a subrepository within this repository"""
400 return False
400 return False
401
401
402 def commit(self, text, user, date):
402 def commit(self, text, user, date):
403 """commit the current changes to the subrepo with the given
403 """commit the current changes to the subrepo with the given
404 log message. Use given user and date if possible. Return the
404 log message. Use given user and date if possible. Return the
405 new state of the subrepo.
405 new state of the subrepo.
406 """
406 """
407 raise NotImplementedError
407 raise NotImplementedError
408
408
409 def phase(self, state):
409 def phase(self, state):
410 """returns phase of specified state in the subrepository.
410 """returns phase of specified state in the subrepository.
411 """
411 """
412 return phases.public
412 return phases.public
413
413
414 def remove(self):
414 def remove(self):
415 """remove the subrepo
415 """remove the subrepo
416
416
417 (should verify the dirstate is not dirty first)
417 (should verify the dirstate is not dirty first)
418 """
418 """
419 raise NotImplementedError
419 raise NotImplementedError
420
420
421 def get(self, state, overwrite=False):
421 def get(self, state, overwrite=False):
422 """run whatever commands are needed to put the subrepo into
422 """run whatever commands are needed to put the subrepo into
423 this state
423 this state
424 """
424 """
425 raise NotImplementedError
425 raise NotImplementedError
426
426
427 def merge(self, state):
427 def merge(self, state):
428 """merge currently-saved state with the new state."""
428 """merge currently-saved state with the new state."""
429 raise NotImplementedError
429 raise NotImplementedError
430
430
431 def push(self, opts):
431 def push(self, opts):
432 """perform whatever action is analogous to 'hg push'
432 """perform whatever action is analogous to 'hg push'
433
433
434 This may be a no-op on some systems.
434 This may be a no-op on some systems.
435 """
435 """
436 raise NotImplementedError
436 raise NotImplementedError
437
437
438 def add(self, ui, match, prefix, explicitonly, **opts):
438 def add(self, ui, match, prefix, explicitonly, **opts):
439 return []
439 return []
440
440
441 def addremove(self, matcher, prefix, opts, dry_run, similarity):
441 def addremove(self, matcher, prefix, opts, dry_run, similarity):
442 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
442 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
443 return 1
443 return 1
444
444
445 def cat(self, match, prefix, **opts):
445 def cat(self, match, prefix, **opts):
446 return 1
446 return 1
447
447
448 def status(self, rev2, **opts):
448 def status(self, rev2, **opts):
449 return scmutil.status([], [], [], [], [], [], [])
449 return scmutil.status([], [], [], [], [], [], [])
450
450
451 def diff(self, ui, diffopts, node2, match, prefix, **opts):
451 def diff(self, ui, diffopts, node2, match, prefix, **opts):
452 pass
452 pass
453
453
454 def outgoing(self, ui, dest, opts):
454 def outgoing(self, ui, dest, opts):
455 return 1
455 return 1
456
456
457 def incoming(self, ui, source, opts):
457 def incoming(self, ui, source, opts):
458 return 1
458 return 1
459
459
460 def files(self):
460 def files(self):
461 """return filename iterator"""
461 """return filename iterator"""
462 raise NotImplementedError
462 raise NotImplementedError
463
463
464 def filedata(self, name):
464 def filedata(self, name):
465 """return file data"""
465 """return file data"""
466 raise NotImplementedError
466 raise NotImplementedError
467
467
468 def fileflags(self, name):
468 def fileflags(self, name):
469 """return file flags"""
469 """return file flags"""
470 return ''
470 return ''
471
471
472 def archive(self, archiver, prefix, match=None):
472 def archive(self, archiver, prefix, match=None):
473 if match is not None:
473 if match is not None:
474 files = [f for f in self.files() if match(f)]
474 files = [f for f in self.files() if match(f)]
475 else:
475 else:
476 files = self.files()
476 files = self.files()
477 total = len(files)
477 total = len(files)
478 relpath = subrelpath(self)
478 relpath = subrelpath(self)
479 self.ui.progress(_('archiving (%s)') % relpath, 0,
479 self.ui.progress(_('archiving (%s)') % relpath, 0,
480 unit=_('files'), total=total)
480 unit=_('files'), total=total)
481 for i, name in enumerate(files):
481 for i, name in enumerate(files):
482 flags = self.fileflags(name)
482 flags = self.fileflags(name)
483 mode = 'x' in flags and 0755 or 0644
483 mode = 'x' in flags and 0755 or 0644
484 symlink = 'l' in flags
484 symlink = 'l' in flags
485 archiver.addfile(os.path.join(prefix, self._path, name),
485 archiver.addfile(os.path.join(prefix, self._path, name),
486 mode, symlink, self.filedata(name))
486 mode, symlink, self.filedata(name))
487 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
487 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
488 unit=_('files'), total=total)
488 unit=_('files'), total=total)
489 self.ui.progress(_('archiving (%s)') % relpath, None)
489 self.ui.progress(_('archiving (%s)') % relpath, None)
490 return total
490 return total
491
491
492 def walk(self, match):
492 def walk(self, match):
493 '''
493 '''
494 walk recursively through the directory tree, finding all files
494 walk recursively through the directory tree, finding all files
495 matched by the match function
495 matched by the match function
496 '''
496 '''
497 pass
497 pass
498
498
499 def forget(self, match, prefix):
499 def forget(self, match, prefix):
500 return ([], [])
500 return ([], [])
501
501
502 def removefiles(self, matcher, prefix, after, force, subrepos):
502 def removefiles(self, matcher, prefix, after, force, subrepos):
503 """remove the matched files from the subrepository and the filesystem,
503 """remove the matched files from the subrepository and the filesystem,
504 possibly by force and/or after the file has been removed from the
504 possibly by force and/or after the file has been removed from the
505 filesystem. Return 0 on success, 1 on any warning.
505 filesystem. Return 0 on success, 1 on any warning.
506 """
506 """
507 return 1
507 return 1
508
508
509 def revert(self, substate, *pats, **opts):
509 def revert(self, substate, *pats, **opts):
510 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
510 self.ui.warn('%s: reverting %s subrepos is unsupported\n' \
511 % (substate[0], substate[2]))
511 % (substate[0], substate[2]))
512 return []
512 return []
513
513
514 def shortid(self, revid):
514 def shortid(self, revid):
515 return revid
515 return revid
516
516
517 class hgsubrepo(abstractsubrepo):
517 class hgsubrepo(abstractsubrepo):
518 def __init__(self, ctx, path, state):
518 def __init__(self, ctx, path, state):
519 super(hgsubrepo, self).__init__(ctx._repo.ui)
519 super(hgsubrepo, self).__init__(ctx._repo.ui)
520 self._path = path
520 self._path = path
521 self._state = state
521 self._state = state
522 r = ctx._repo
522 r = ctx._repo
523 root = r.wjoin(path)
523 root = r.wjoin(path)
524 create = not r.wvfs.exists('%s/.hg' % path)
524 create = not r.wvfs.exists('%s/.hg' % path)
525 self._repo = hg.repository(r.baseui, root, create=create)
525 self._repo = hg.repository(r.baseui, root, create=create)
526 self.ui = self._repo.ui
526 self.ui = self._repo.ui
527 for s, k in [('ui', 'commitsubrepos')]:
527 for s, k in [('ui', 'commitsubrepos')]:
528 v = r.ui.config(s, k)
528 v = r.ui.config(s, k)
529 if v:
529 if v:
530 self.ui.setconfig(s, k, v, 'subrepo')
530 self.ui.setconfig(s, k, v, 'subrepo')
531 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
531 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
532 self._initrepo(r, state[0], create)
532 self._initrepo(r, state[0], create)
533
533
534 def storeclean(self, path):
534 def storeclean(self, path):
535 lock = self._repo.lock()
535 lock = self._repo.lock()
536 try:
536 try:
537 return self._storeclean(path)
537 return self._storeclean(path)
538 finally:
538 finally:
539 lock.release()
539 lock.release()
540
540
541 def _storeclean(self, path):
541 def _storeclean(self, path):
542 clean = True
542 clean = True
543 itercache = self._calcstorehash(path)
543 itercache = self._calcstorehash(path)
544 try:
544 try:
545 for filehash in self._readstorehashcache(path):
545 for filehash in self._readstorehashcache(path):
546 if filehash != itercache.next():
546 if filehash != itercache.next():
547 clean = False
547 clean = False
548 break
548 break
549 except StopIteration:
549 except StopIteration:
550 # the cached and current pull states have a different size
550 # the cached and current pull states have a different size
551 clean = False
551 clean = False
552 if clean:
552 if clean:
553 try:
553 try:
554 itercache.next()
554 itercache.next()
555 # the cached and current pull states have a different size
555 # the cached and current pull states have a different size
556 clean = False
556 clean = False
557 except StopIteration:
557 except StopIteration:
558 pass
558 pass
559 return clean
559 return clean
560
560
561 def _calcstorehash(self, remotepath):
561 def _calcstorehash(self, remotepath):
562 '''calculate a unique "store hash"
562 '''calculate a unique "store hash"
563
563
564 This method is used to to detect when there are changes that may
564 This method is used to to detect when there are changes that may
565 require a push to a given remote path.'''
565 require a push to a given remote path.'''
566 # sort the files that will be hashed in increasing (likely) file size
566 # sort the files that will be hashed in increasing (likely) file size
567 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
567 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
568 yield '# %s\n' % _expandedabspath(remotepath)
568 yield '# %s\n' % _expandedabspath(remotepath)
569 vfs = self._repo.vfs
569 vfs = self._repo.vfs
570 for relname in filelist:
570 for relname in filelist:
571 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
571 filehash = util.sha1(vfs.tryread(relname)).hexdigest()
572 yield '%s = %s\n' % (relname, filehash)
572 yield '%s = %s\n' % (relname, filehash)
573
573
574 @propertycache
574 @propertycache
575 def _cachestorehashvfs(self):
575 def _cachestorehashvfs(self):
576 return scmutil.vfs(self._repo.join('cache/storehash'))
576 return scmutil.vfs(self._repo.join('cache/storehash'))
577
577
578 def _readstorehashcache(self, remotepath):
578 def _readstorehashcache(self, remotepath):
579 '''read the store hash cache for a given remote repository'''
579 '''read the store hash cache for a given remote repository'''
580 cachefile = _getstorehashcachename(remotepath)
580 cachefile = _getstorehashcachename(remotepath)
581 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
581 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
582
582
583 def _cachestorehash(self, remotepath):
583 def _cachestorehash(self, remotepath):
584 '''cache the current store hash
584 '''cache the current store hash
585
585
586 Each remote repo requires its own store hash cache, because a subrepo
586 Each remote repo requires its own store hash cache, because a subrepo
587 store may be "clean" versus a given remote repo, but not versus another
587 store may be "clean" versus a given remote repo, but not versus another
588 '''
588 '''
589 cachefile = _getstorehashcachename(remotepath)
589 cachefile = _getstorehashcachename(remotepath)
590 lock = self._repo.lock()
590 lock = self._repo.lock()
591 try:
591 try:
592 storehash = list(self._calcstorehash(remotepath))
592 storehash = list(self._calcstorehash(remotepath))
593 vfs = self._cachestorehashvfs
593 vfs = self._cachestorehashvfs
594 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
594 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
595 finally:
595 finally:
596 lock.release()
596 lock.release()
597
597
598 @annotatesubrepoerror
598 @annotatesubrepoerror
599 def _initrepo(self, parentrepo, source, create):
599 def _initrepo(self, parentrepo, source, create):
600 self._repo._subparent = parentrepo
600 self._repo._subparent = parentrepo
601 self._repo._subsource = source
601 self._repo._subsource = source
602
602
603 if create:
603 if create:
604 lines = ['[paths]\n']
604 lines = ['[paths]\n']
605
605
606 def addpathconfig(key, value):
606 def addpathconfig(key, value):
607 if value:
607 if value:
608 lines.append('%s = %s\n' % (key, value))
608 lines.append('%s = %s\n' % (key, value))
609 self.ui.setconfig('paths', key, value, 'subrepo')
609 self.ui.setconfig('paths', key, value, 'subrepo')
610
610
611 defpath = _abssource(self._repo, abort=False)
611 defpath = _abssource(self._repo, abort=False)
612 defpushpath = _abssource(self._repo, True, abort=False)
612 defpushpath = _abssource(self._repo, True, abort=False)
613 addpathconfig('default', defpath)
613 addpathconfig('default', defpath)
614 if defpath != defpushpath:
614 if defpath != defpushpath:
615 addpathconfig('default-push', defpushpath)
615 addpathconfig('default-push', defpushpath)
616
616
617 fp = self._repo.vfs("hgrc", "w", text=True)
617 fp = self._repo.vfs("hgrc", "w", text=True)
618 try:
618 try:
619 fp.write(''.join(lines))
619 fp.write(''.join(lines))
620 finally:
620 finally:
621 fp.close()
621 fp.close()
622
622
623 @annotatesubrepoerror
623 @annotatesubrepoerror
624 def add(self, ui, match, prefix, explicitonly, **opts):
624 def add(self, ui, match, prefix, explicitonly, **opts):
625 return cmdutil.add(ui, self._repo, match,
625 return cmdutil.add(ui, self._repo, match,
626 os.path.join(prefix, self._path), explicitonly,
626 os.path.join(prefix, self._path), explicitonly,
627 **opts)
627 **opts)
628
628
629 @annotatesubrepoerror
629 @annotatesubrepoerror
630 def addremove(self, m, prefix, opts, dry_run, similarity):
630 def addremove(self, m, prefix, opts, dry_run, similarity):
631 # In the same way as sub directories are processed, once in a subrepo,
631 # In the same way as sub directories are processed, once in a subrepo,
632 # always entry any of its subrepos. Don't corrupt the options that will
632 # always entry any of its subrepos. Don't corrupt the options that will
633 # be used to process sibling subrepos however.
633 # be used to process sibling subrepos however.
634 opts = copy.copy(opts)
634 opts = copy.copy(opts)
635 opts['subrepos'] = True
635 opts['subrepos'] = True
636 return scmutil.addremove(self._repo, m,
636 return scmutil.addremove(self._repo, m,
637 os.path.join(prefix, self._path), opts,
637 os.path.join(prefix, self._path), opts,
638 dry_run, similarity)
638 dry_run, similarity)
639
639
640 @annotatesubrepoerror
640 @annotatesubrepoerror
641 def cat(self, match, prefix, **opts):
641 def cat(self, match, prefix, **opts):
642 rev = self._state[1]
642 rev = self._state[1]
643 ctx = self._repo[rev]
643 ctx = self._repo[rev]
644 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
644 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
645
645
646 @annotatesubrepoerror
646 @annotatesubrepoerror
647 def status(self, rev2, **opts):
647 def status(self, rev2, **opts):
648 try:
648 try:
649 rev1 = self._state[1]
649 rev1 = self._state[1]
650 ctx1 = self._repo[rev1]
650 ctx1 = self._repo[rev1]
651 ctx2 = self._repo[rev2]
651 ctx2 = self._repo[rev2]
652 return self._repo.status(ctx1, ctx2, **opts)
652 return self._repo.status(ctx1, ctx2, **opts)
653 except error.RepoLookupError, inst:
653 except error.RepoLookupError, inst:
654 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
654 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
655 % (inst, subrelpath(self)))
655 % (inst, subrelpath(self)))
656 return scmutil.status([], [], [], [], [], [], [])
656 return scmutil.status([], [], [], [], [], [], [])
657
657
658 @annotatesubrepoerror
658 @annotatesubrepoerror
659 def diff(self, ui, diffopts, node2, match, prefix, **opts):
659 def diff(self, ui, diffopts, node2, match, prefix, **opts):
660 try:
660 try:
661 node1 = node.bin(self._state[1])
661 node1 = node.bin(self._state[1])
662 # We currently expect node2 to come from substate and be
662 # We currently expect node2 to come from substate and be
663 # in hex format
663 # in hex format
664 if node2 is not None:
664 if node2 is not None:
665 node2 = node.bin(node2)
665 node2 = node.bin(node2)
666 cmdutil.diffordiffstat(ui, self._repo, diffopts,
666 cmdutil.diffordiffstat(ui, self._repo, diffopts,
667 node1, node2, match,
667 node1, node2, match,
668 prefix=posixpath.join(prefix, self._path),
668 prefix=posixpath.join(prefix, self._path),
669 listsubrepos=True, **opts)
669 listsubrepos=True, **opts)
670 except error.RepoLookupError, inst:
670 except error.RepoLookupError, inst:
671 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
671 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
672 % (inst, subrelpath(self)))
672 % (inst, subrelpath(self)))
673
673
674 @annotatesubrepoerror
674 @annotatesubrepoerror
675 def archive(self, archiver, prefix, match=None):
675 def archive(self, archiver, prefix, match=None):
676 self._get(self._state + ('hg',))
676 self._get(self._state + ('hg',))
677 total = abstractsubrepo.archive(self, archiver, prefix, match)
677 total = abstractsubrepo.archive(self, archiver, prefix, match)
678 rev = self._state[1]
678 rev = self._state[1]
679 ctx = self._repo[rev]
679 ctx = self._repo[rev]
680 for subpath in ctx.substate:
680 for subpath in ctx.substate:
681 s = subrepo(ctx, subpath)
681 s = subrepo(ctx, subpath)
682 submatch = matchmod.narrowmatcher(subpath, match)
682 submatch = matchmod.narrowmatcher(subpath, match)
683 total += s.archive(
683 total += s.archive(
684 archiver, os.path.join(prefix, self._path), submatch)
684 archiver, os.path.join(prefix, self._path), submatch)
685 return total
685 return total
686
686
687 @annotatesubrepoerror
687 @annotatesubrepoerror
688 def dirty(self, ignoreupdate=False):
688 def dirty(self, ignoreupdate=False):
689 r = self._state[1]
689 r = self._state[1]
690 if r == '' and not ignoreupdate: # no state recorded
690 if r == '' and not ignoreupdate: # no state recorded
691 return True
691 return True
692 w = self._repo[None]
692 w = self._repo[None]
693 if r != w.p1().hex() and not ignoreupdate:
693 if r != w.p1().hex() and not ignoreupdate:
694 # different version checked out
694 # different version checked out
695 return True
695 return True
696 return w.dirty() # working directory changed
696 return w.dirty() # working directory changed
697
697
698 def basestate(self):
698 def basestate(self):
699 return self._repo['.'].hex()
699 return self._repo['.'].hex()
700
700
701 def checknested(self, path):
701 def checknested(self, path):
702 return self._repo._checknested(self._repo.wjoin(path))
702 return self._repo._checknested(self._repo.wjoin(path))
703
703
704 @annotatesubrepoerror
704 @annotatesubrepoerror
705 def commit(self, text, user, date):
705 def commit(self, text, user, date):
706 # don't bother committing in the subrepo if it's only been
706 # don't bother committing in the subrepo if it's only been
707 # updated
707 # updated
708 if not self.dirty(True):
708 if not self.dirty(True):
709 return self._repo['.'].hex()
709 return self._repo['.'].hex()
710 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
710 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
711 n = self._repo.commit(text, user, date)
711 n = self._repo.commit(text, user, date)
712 if not n:
712 if not n:
713 return self._repo['.'].hex() # different version checked out
713 return self._repo['.'].hex() # different version checked out
714 return node.hex(n)
714 return node.hex(n)
715
715
716 @annotatesubrepoerror
716 @annotatesubrepoerror
717 def phase(self, state):
717 def phase(self, state):
718 return self._repo[state].phase()
718 return self._repo[state].phase()
719
719
720 @annotatesubrepoerror
720 @annotatesubrepoerror
721 def remove(self):
721 def remove(self):
722 # we can't fully delete the repository as it may contain
722 # we can't fully delete the repository as it may contain
723 # local-only history
723 # local-only history
724 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
724 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
725 hg.clean(self._repo, node.nullid, False)
725 hg.clean(self._repo, node.nullid, False)
726
726
727 def _get(self, state):
727 def _get(self, state):
728 source, revision, kind = state
728 source, revision, kind = state
729 if revision in self._repo.unfiltered():
729 if revision in self._repo.unfiltered():
730 return True
730 return True
731 self._repo._subsource = source
731 self._repo._subsource = source
732 srcurl = _abssource(self._repo)
732 srcurl = _abssource(self._repo)
733 other = hg.peer(self._repo, {}, srcurl)
733 other = hg.peer(self._repo, {}, srcurl)
734 if len(self._repo) == 0:
734 if len(self._repo) == 0:
735 self.ui.status(_('cloning subrepo %s from %s\n')
735 self.ui.status(_('cloning subrepo %s from %s\n')
736 % (subrelpath(self), srcurl))
736 % (subrelpath(self), srcurl))
737 parentrepo = self._repo._subparent
737 parentrepo = self._repo._subparent
738 shutil.rmtree(self._repo.path)
738 shutil.rmtree(self._repo.path)
739 other, cloned = hg.clone(self._repo._subparent.baseui, {},
739 other, cloned = hg.clone(self._repo._subparent.baseui, {},
740 other, self._repo.root,
740 other, self._repo.root,
741 update=False)
741 update=False)
742 self._repo = cloned.local()
742 self._repo = cloned.local()
743 self._initrepo(parentrepo, source, create=True)
743 self._initrepo(parentrepo, source, create=True)
744 self._cachestorehash(srcurl)
744 self._cachestorehash(srcurl)
745 else:
745 else:
746 self.ui.status(_('pulling subrepo %s from %s\n')
746 self.ui.status(_('pulling subrepo %s from %s\n')
747 % (subrelpath(self), srcurl))
747 % (subrelpath(self), srcurl))
748 cleansub = self.storeclean(srcurl)
748 cleansub = self.storeclean(srcurl)
749 exchange.pull(self._repo, other)
749 exchange.pull(self._repo, other)
750 if cleansub:
750 if cleansub:
751 # keep the repo clean after pull
751 # keep the repo clean after pull
752 self._cachestorehash(srcurl)
752 self._cachestorehash(srcurl)
753 return False
753 return False
754
754
755 @annotatesubrepoerror
755 @annotatesubrepoerror
756 def get(self, state, overwrite=False):
756 def get(self, state, overwrite=False):
757 inrepo = self._get(state)
757 inrepo = self._get(state)
758 source, revision, kind = state
758 source, revision, kind = state
759 repo = self._repo
759 repo = self._repo
760 repo.ui.debug("getting subrepo %s\n" % self._path)
760 repo.ui.debug("getting subrepo %s\n" % self._path)
761 if inrepo:
761 if inrepo:
762 urepo = repo.unfiltered()
762 urepo = repo.unfiltered()
763 ctx = urepo[revision]
763 ctx = urepo[revision]
764 if ctx.hidden():
764 if ctx.hidden():
765 urepo.ui.warn(
765 urepo.ui.warn(
766 _('revision %s in subrepo %s is hidden\n') \
766 _('revision %s in subrepo %s is hidden\n') \
767 % (revision[0:12], self._path))
767 % (revision[0:12], self._path))
768 repo = urepo
768 repo = urepo
769 hg.updaterepo(repo, revision, overwrite)
769 hg.updaterepo(repo, revision, overwrite)
770
770
771 @annotatesubrepoerror
771 @annotatesubrepoerror
772 def merge(self, state):
772 def merge(self, state):
773 self._get(state)
773 self._get(state)
774 cur = self._repo['.']
774 cur = self._repo['.']
775 dst = self._repo[state[1]]
775 dst = self._repo[state[1]]
776 anc = dst.ancestor(cur)
776 anc = dst.ancestor(cur)
777
777
778 def mergefunc():
778 def mergefunc():
779 if anc == cur and dst.branch() == cur.branch():
779 if anc == cur and dst.branch() == cur.branch():
780 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
780 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
781 hg.update(self._repo, state[1])
781 hg.update(self._repo, state[1])
782 elif anc == dst:
782 elif anc == dst:
783 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
783 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
784 else:
784 else:
785 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
785 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
786 hg.merge(self._repo, state[1], remind=False)
786 hg.merge(self._repo, state[1], remind=False)
787
787
788 wctx = self._repo[None]
788 wctx = self._repo[None]
789 if self.dirty():
789 if self.dirty():
790 if anc != dst:
790 if anc != dst:
791 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
791 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
792 mergefunc()
792 mergefunc()
793 else:
793 else:
794 mergefunc()
794 mergefunc()
795 else:
795 else:
796 mergefunc()
796 mergefunc()
797
797
798 @annotatesubrepoerror
798 @annotatesubrepoerror
799 def push(self, opts):
799 def push(self, opts):
800 force = opts.get('force')
800 force = opts.get('force')
801 newbranch = opts.get('new_branch')
801 newbranch = opts.get('new_branch')
802 ssh = opts.get('ssh')
802 ssh = opts.get('ssh')
803
803
804 # push subrepos depth-first for coherent ordering
804 # push subrepos depth-first for coherent ordering
805 c = self._repo['']
805 c = self._repo['']
806 subs = c.substate # only repos that are committed
806 subs = c.substate # only repos that are committed
807 for s in sorted(subs):
807 for s in sorted(subs):
808 if c.sub(s).push(opts) == 0:
808 if c.sub(s).push(opts) == 0:
809 return False
809 return False
810
810
811 dsturl = _abssource(self._repo, True)
811 dsturl = _abssource(self._repo, True)
812 if not force:
812 if not force:
813 if self.storeclean(dsturl):
813 if self.storeclean(dsturl):
814 self.ui.status(
814 self.ui.status(
815 _('no changes made to subrepo %s since last push to %s\n')
815 _('no changes made to subrepo %s since last push to %s\n')
816 % (subrelpath(self), dsturl))
816 % (subrelpath(self), dsturl))
817 return None
817 return None
818 self.ui.status(_('pushing subrepo %s to %s\n') %
818 self.ui.status(_('pushing subrepo %s to %s\n') %
819 (subrelpath(self), dsturl))
819 (subrelpath(self), dsturl))
820 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
820 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
821 res = exchange.push(self._repo, other, force, newbranch=newbranch)
821 res = exchange.push(self._repo, other, force, newbranch=newbranch)
822
822
823 # the repo is now clean
823 # the repo is now clean
824 self._cachestorehash(dsturl)
824 self._cachestorehash(dsturl)
825 return res.cgresult
825 return res.cgresult
826
826
827 @annotatesubrepoerror
827 @annotatesubrepoerror
828 def outgoing(self, ui, dest, opts):
828 def outgoing(self, ui, dest, opts):
829 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
829 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
830
830
831 @annotatesubrepoerror
831 @annotatesubrepoerror
832 def incoming(self, ui, source, opts):
832 def incoming(self, ui, source, opts):
833 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
833 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
834
834
835 @annotatesubrepoerror
835 @annotatesubrepoerror
836 def files(self):
836 def files(self):
837 rev = self._state[1]
837 rev = self._state[1]
838 ctx = self._repo[rev]
838 ctx = self._repo[rev]
839 return ctx.manifest()
839 return ctx.manifest()
840
840
841 def filedata(self, name):
841 def filedata(self, name):
842 rev = self._state[1]
842 rev = self._state[1]
843 return self._repo[rev][name].data()
843 return self._repo[rev][name].data()
844
844
845 def fileflags(self, name):
845 def fileflags(self, name):
846 rev = self._state[1]
846 rev = self._state[1]
847 ctx = self._repo[rev]
847 ctx = self._repo[rev]
848 return ctx.flags(name)
848 return ctx.flags(name)
849
849
850 def walk(self, match):
850 def walk(self, match):
851 ctx = self._repo[None]
851 ctx = self._repo[None]
852 return ctx.walk(match)
852 return ctx.walk(match)
853
853
854 @annotatesubrepoerror
854 @annotatesubrepoerror
855 def forget(self, match, prefix):
855 def forget(self, match, prefix):
856 return cmdutil.forget(self.ui, self._repo, match,
856 return cmdutil.forget(self.ui, self._repo, match,
857 os.path.join(prefix, self._path), True)
857 os.path.join(prefix, self._path), True)
858
858
859 @annotatesubrepoerror
859 @annotatesubrepoerror
860 def removefiles(self, matcher, prefix, after, force, subrepos):
860 def removefiles(self, matcher, prefix, after, force, subrepos):
861 return cmdutil.remove(self.ui, self._repo, matcher,
861 return cmdutil.remove(self.ui, self._repo, matcher,
862 os.path.join(prefix, self._path), after, force,
862 os.path.join(prefix, self._path), after, force,
863 subrepos)
863 subrepos)
864
864
865 @annotatesubrepoerror
865 @annotatesubrepoerror
866 def revert(self, substate, *pats, **opts):
866 def revert(self, substate, *pats, **opts):
867 # reverting a subrepo is a 2 step process:
867 # reverting a subrepo is a 2 step process:
868 # 1. if the no_backup is not set, revert all modified
868 # 1. if the no_backup is not set, revert all modified
869 # files inside the subrepo
869 # files inside the subrepo
870 # 2. update the subrepo to the revision specified in
870 # 2. update the subrepo to the revision specified in
871 # the corresponding substate dictionary
871 # the corresponding substate dictionary
872 self.ui.status(_('reverting subrepo %s\n') % substate[0])
872 self.ui.status(_('reverting subrepo %s\n') % substate[0])
873 if not opts.get('no_backup'):
873 if not opts.get('no_backup'):
874 # Revert all files on the subrepo, creating backups
874 # Revert all files on the subrepo, creating backups
875 # Note that this will not recursively revert subrepos
875 # Note that this will not recursively revert subrepos
876 # We could do it if there was a set:subrepos() predicate
876 # We could do it if there was a set:subrepos() predicate
877 opts = opts.copy()
877 opts = opts.copy()
878 opts['date'] = None
878 opts['date'] = None
879 opts['rev'] = substate[1]
879 opts['rev'] = substate[1]
880
880
881 pats = []
881 pats = []
882 if not opts.get('all'):
882 if not opts.get('all'):
883 pats = ['set:modified()']
883 pats = ['set:modified()']
884 self.filerevert(*pats, **opts)
884 self.filerevert(*pats, **opts)
885
885
886 # Update the repo to the revision specified in the given substate
886 # Update the repo to the revision specified in the given substate
887 self.get(substate, overwrite=True)
887 if not opts.get('dry_run'):
888 self.get(substate, overwrite=True)
888
889
889 def filerevert(self, *pats, **opts):
890 def filerevert(self, *pats, **opts):
890 ctx = self._repo[opts['rev']]
891 ctx = self._repo[opts['rev']]
891 parents = self._repo.dirstate.parents()
892 parents = self._repo.dirstate.parents()
892 if opts.get('all'):
893 if opts.get('all'):
893 pats = ['set:modified()']
894 pats = ['set:modified()']
894 else:
895 else:
895 pats = []
896 pats = []
896 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
897 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
897
898
898 def shortid(self, revid):
899 def shortid(self, revid):
899 return revid[:12]
900 return revid[:12]
900
901
901 class svnsubrepo(abstractsubrepo):
902 class svnsubrepo(abstractsubrepo):
902 def __init__(self, ctx, path, state):
903 def __init__(self, ctx, path, state):
903 super(svnsubrepo, self).__init__(ctx._repo.ui)
904 super(svnsubrepo, self).__init__(ctx._repo.ui)
904 self._path = path
905 self._path = path
905 self._state = state
906 self._state = state
906 self._ctx = ctx
907 self._ctx = ctx
907 self._exe = util.findexe('svn')
908 self._exe = util.findexe('svn')
908 if not self._exe:
909 if not self._exe:
909 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
910 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
910 % self._path)
911 % self._path)
911
912
912 def _svncommand(self, commands, filename='', failok=False):
913 def _svncommand(self, commands, filename='', failok=False):
913 cmd = [self._exe]
914 cmd = [self._exe]
914 extrakw = {}
915 extrakw = {}
915 if not self.ui.interactive():
916 if not self.ui.interactive():
916 # Making stdin be a pipe should prevent svn from behaving
917 # Making stdin be a pipe should prevent svn from behaving
917 # interactively even if we can't pass --non-interactive.
918 # interactively even if we can't pass --non-interactive.
918 extrakw['stdin'] = subprocess.PIPE
919 extrakw['stdin'] = subprocess.PIPE
919 # Starting in svn 1.5 --non-interactive is a global flag
920 # Starting in svn 1.5 --non-interactive is a global flag
920 # instead of being per-command, but we need to support 1.4 so
921 # instead of being per-command, but we need to support 1.4 so
921 # we have to be intelligent about what commands take
922 # we have to be intelligent about what commands take
922 # --non-interactive.
923 # --non-interactive.
923 if commands[0] in ('update', 'checkout', 'commit'):
924 if commands[0] in ('update', 'checkout', 'commit'):
924 cmd.append('--non-interactive')
925 cmd.append('--non-interactive')
925 cmd.extend(commands)
926 cmd.extend(commands)
926 if filename is not None:
927 if filename is not None:
927 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
928 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
928 cmd.append(path)
929 cmd.append(path)
929 env = dict(os.environ)
930 env = dict(os.environ)
930 # Avoid localized output, preserve current locale for everything else.
931 # Avoid localized output, preserve current locale for everything else.
931 lc_all = env.get('LC_ALL')
932 lc_all = env.get('LC_ALL')
932 if lc_all:
933 if lc_all:
933 env['LANG'] = lc_all
934 env['LANG'] = lc_all
934 del env['LC_ALL']
935 del env['LC_ALL']
935 env['LC_MESSAGES'] = 'C'
936 env['LC_MESSAGES'] = 'C'
936 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
937 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
937 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
938 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
938 universal_newlines=True, env=env, **extrakw)
939 universal_newlines=True, env=env, **extrakw)
939 stdout, stderr = p.communicate()
940 stdout, stderr = p.communicate()
940 stderr = stderr.strip()
941 stderr = stderr.strip()
941 if not failok:
942 if not failok:
942 if p.returncode:
943 if p.returncode:
943 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
944 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
944 if stderr:
945 if stderr:
945 self.ui.warn(stderr + '\n')
946 self.ui.warn(stderr + '\n')
946 return stdout, stderr
947 return stdout, stderr
947
948
948 @propertycache
949 @propertycache
949 def _svnversion(self):
950 def _svnversion(self):
950 output, err = self._svncommand(['--version', '--quiet'], filename=None)
951 output, err = self._svncommand(['--version', '--quiet'], filename=None)
951 m = re.search(r'^(\d+)\.(\d+)', output)
952 m = re.search(r'^(\d+)\.(\d+)', output)
952 if not m:
953 if not m:
953 raise util.Abort(_('cannot retrieve svn tool version'))
954 raise util.Abort(_('cannot retrieve svn tool version'))
954 return (int(m.group(1)), int(m.group(2)))
955 return (int(m.group(1)), int(m.group(2)))
955
956
956 def _wcrevs(self):
957 def _wcrevs(self):
957 # Get the working directory revision as well as the last
958 # Get the working directory revision as well as the last
958 # commit revision so we can compare the subrepo state with
959 # commit revision so we can compare the subrepo state with
959 # both. We used to store the working directory one.
960 # both. We used to store the working directory one.
960 output, err = self._svncommand(['info', '--xml'])
961 output, err = self._svncommand(['info', '--xml'])
961 doc = xml.dom.minidom.parseString(output)
962 doc = xml.dom.minidom.parseString(output)
962 entries = doc.getElementsByTagName('entry')
963 entries = doc.getElementsByTagName('entry')
963 lastrev, rev = '0', '0'
964 lastrev, rev = '0', '0'
964 if entries:
965 if entries:
965 rev = str(entries[0].getAttribute('revision')) or '0'
966 rev = str(entries[0].getAttribute('revision')) or '0'
966 commits = entries[0].getElementsByTagName('commit')
967 commits = entries[0].getElementsByTagName('commit')
967 if commits:
968 if commits:
968 lastrev = str(commits[0].getAttribute('revision')) or '0'
969 lastrev = str(commits[0].getAttribute('revision')) or '0'
969 return (lastrev, rev)
970 return (lastrev, rev)
970
971
971 def _wcrev(self):
972 def _wcrev(self):
972 return self._wcrevs()[0]
973 return self._wcrevs()[0]
973
974
974 def _wcchanged(self):
975 def _wcchanged(self):
975 """Return (changes, extchanges, missing) where changes is True
976 """Return (changes, extchanges, missing) where changes is True
976 if the working directory was changed, extchanges is
977 if the working directory was changed, extchanges is
977 True if any of these changes concern an external entry and missing
978 True if any of these changes concern an external entry and missing
978 is True if any change is a missing entry.
979 is True if any change is a missing entry.
979 """
980 """
980 output, err = self._svncommand(['status', '--xml'])
981 output, err = self._svncommand(['status', '--xml'])
981 externals, changes, missing = [], [], []
982 externals, changes, missing = [], [], []
982 doc = xml.dom.minidom.parseString(output)
983 doc = xml.dom.minidom.parseString(output)
983 for e in doc.getElementsByTagName('entry'):
984 for e in doc.getElementsByTagName('entry'):
984 s = e.getElementsByTagName('wc-status')
985 s = e.getElementsByTagName('wc-status')
985 if not s:
986 if not s:
986 continue
987 continue
987 item = s[0].getAttribute('item')
988 item = s[0].getAttribute('item')
988 props = s[0].getAttribute('props')
989 props = s[0].getAttribute('props')
989 path = e.getAttribute('path')
990 path = e.getAttribute('path')
990 if item == 'external':
991 if item == 'external':
991 externals.append(path)
992 externals.append(path)
992 elif item == 'missing':
993 elif item == 'missing':
993 missing.append(path)
994 missing.append(path)
994 if (item not in ('', 'normal', 'unversioned', 'external')
995 if (item not in ('', 'normal', 'unversioned', 'external')
995 or props not in ('', 'none', 'normal')):
996 or props not in ('', 'none', 'normal')):
996 changes.append(path)
997 changes.append(path)
997 for path in changes:
998 for path in changes:
998 for ext in externals:
999 for ext in externals:
999 if path == ext or path.startswith(ext + os.sep):
1000 if path == ext or path.startswith(ext + os.sep):
1000 return True, True, bool(missing)
1001 return True, True, bool(missing)
1001 return bool(changes), False, bool(missing)
1002 return bool(changes), False, bool(missing)
1002
1003
1003 def dirty(self, ignoreupdate=False):
1004 def dirty(self, ignoreupdate=False):
1004 if not self._wcchanged()[0]:
1005 if not self._wcchanged()[0]:
1005 if self._state[1] in self._wcrevs() or ignoreupdate:
1006 if self._state[1] in self._wcrevs() or ignoreupdate:
1006 return False
1007 return False
1007 return True
1008 return True
1008
1009
1009 def basestate(self):
1010 def basestate(self):
1010 lastrev, rev = self._wcrevs()
1011 lastrev, rev = self._wcrevs()
1011 if lastrev != rev:
1012 if lastrev != rev:
1012 # Last committed rev is not the same than rev. We would
1013 # Last committed rev is not the same than rev. We would
1013 # like to take lastrev but we do not know if the subrepo
1014 # like to take lastrev but we do not know if the subrepo
1014 # URL exists at lastrev. Test it and fallback to rev it
1015 # URL exists at lastrev. Test it and fallback to rev it
1015 # is not there.
1016 # is not there.
1016 try:
1017 try:
1017 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1018 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1018 return lastrev
1019 return lastrev
1019 except error.Abort:
1020 except error.Abort:
1020 pass
1021 pass
1021 return rev
1022 return rev
1022
1023
1023 @annotatesubrepoerror
1024 @annotatesubrepoerror
1024 def commit(self, text, user, date):
1025 def commit(self, text, user, date):
1025 # user and date are out of our hands since svn is centralized
1026 # user and date are out of our hands since svn is centralized
1026 changed, extchanged, missing = self._wcchanged()
1027 changed, extchanged, missing = self._wcchanged()
1027 if not changed:
1028 if not changed:
1028 return self.basestate()
1029 return self.basestate()
1029 if extchanged:
1030 if extchanged:
1030 # Do not try to commit externals
1031 # Do not try to commit externals
1031 raise util.Abort(_('cannot commit svn externals'))
1032 raise util.Abort(_('cannot commit svn externals'))
1032 if missing:
1033 if missing:
1033 # svn can commit with missing entries but aborting like hg
1034 # svn can commit with missing entries but aborting like hg
1034 # seems a better approach.
1035 # seems a better approach.
1035 raise util.Abort(_('cannot commit missing svn entries'))
1036 raise util.Abort(_('cannot commit missing svn entries'))
1036 commitinfo, err = self._svncommand(['commit', '-m', text])
1037 commitinfo, err = self._svncommand(['commit', '-m', text])
1037 self.ui.status(commitinfo)
1038 self.ui.status(commitinfo)
1038 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1039 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1039 if not newrev:
1040 if not newrev:
1040 if not commitinfo.strip():
1041 if not commitinfo.strip():
1041 # Sometimes, our definition of "changed" differs from
1042 # Sometimes, our definition of "changed" differs from
1042 # svn one. For instance, svn ignores missing files
1043 # svn one. For instance, svn ignores missing files
1043 # when committing. If there are only missing files, no
1044 # when committing. If there are only missing files, no
1044 # commit is made, no output and no error code.
1045 # commit is made, no output and no error code.
1045 raise util.Abort(_('failed to commit svn changes'))
1046 raise util.Abort(_('failed to commit svn changes'))
1046 raise util.Abort(commitinfo.splitlines()[-1])
1047 raise util.Abort(commitinfo.splitlines()[-1])
1047 newrev = newrev.groups()[0]
1048 newrev = newrev.groups()[0]
1048 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1049 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1049 return newrev
1050 return newrev
1050
1051
1051 @annotatesubrepoerror
1052 @annotatesubrepoerror
1052 def remove(self):
1053 def remove(self):
1053 if self.dirty():
1054 if self.dirty():
1054 self.ui.warn(_('not removing repo %s because '
1055 self.ui.warn(_('not removing repo %s because '
1055 'it has changes.\n') % self._path)
1056 'it has changes.\n') % self._path)
1056 return
1057 return
1057 self.ui.note(_('removing subrepo %s\n') % self._path)
1058 self.ui.note(_('removing subrepo %s\n') % self._path)
1058
1059
1059 def onerror(function, path, excinfo):
1060 def onerror(function, path, excinfo):
1060 if function is not os.remove:
1061 if function is not os.remove:
1061 raise
1062 raise
1062 # read-only files cannot be unlinked under Windows
1063 # read-only files cannot be unlinked under Windows
1063 s = os.stat(path)
1064 s = os.stat(path)
1064 if (s.st_mode & stat.S_IWRITE) != 0:
1065 if (s.st_mode & stat.S_IWRITE) != 0:
1065 raise
1066 raise
1066 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
1067 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
1067 os.remove(path)
1068 os.remove(path)
1068
1069
1069 path = self._ctx._repo.wjoin(self._path)
1070 path = self._ctx._repo.wjoin(self._path)
1070 shutil.rmtree(path, onerror=onerror)
1071 shutil.rmtree(path, onerror=onerror)
1071 try:
1072 try:
1072 os.removedirs(os.path.dirname(path))
1073 os.removedirs(os.path.dirname(path))
1073 except OSError:
1074 except OSError:
1074 pass
1075 pass
1075
1076
1076 @annotatesubrepoerror
1077 @annotatesubrepoerror
1077 def get(self, state, overwrite=False):
1078 def get(self, state, overwrite=False):
1078 if overwrite:
1079 if overwrite:
1079 self._svncommand(['revert', '--recursive'])
1080 self._svncommand(['revert', '--recursive'])
1080 args = ['checkout']
1081 args = ['checkout']
1081 if self._svnversion >= (1, 5):
1082 if self._svnversion >= (1, 5):
1082 args.append('--force')
1083 args.append('--force')
1083 # The revision must be specified at the end of the URL to properly
1084 # The revision must be specified at the end of the URL to properly
1084 # update to a directory which has since been deleted and recreated.
1085 # update to a directory which has since been deleted and recreated.
1085 args.append('%s@%s' % (state[0], state[1]))
1086 args.append('%s@%s' % (state[0], state[1]))
1086 status, err = self._svncommand(args, failok=True)
1087 status, err = self._svncommand(args, failok=True)
1087 _sanitize(self.ui, self._ctx._repo.wjoin(self._path), '.svn')
1088 _sanitize(self.ui, self._ctx._repo.wjoin(self._path), '.svn')
1088 if not re.search('Checked out revision [0-9]+.', status):
1089 if not re.search('Checked out revision [0-9]+.', status):
1089 if ('is already a working copy for a different URL' in err
1090 if ('is already a working copy for a different URL' in err
1090 and (self._wcchanged()[:2] == (False, False))):
1091 and (self._wcchanged()[:2] == (False, False))):
1091 # obstructed but clean working copy, so just blow it away.
1092 # obstructed but clean working copy, so just blow it away.
1092 self.remove()
1093 self.remove()
1093 self.get(state, overwrite=False)
1094 self.get(state, overwrite=False)
1094 return
1095 return
1095 raise util.Abort((status or err).splitlines()[-1])
1096 raise util.Abort((status or err).splitlines()[-1])
1096 self.ui.status(status)
1097 self.ui.status(status)
1097
1098
1098 @annotatesubrepoerror
1099 @annotatesubrepoerror
1099 def merge(self, state):
1100 def merge(self, state):
1100 old = self._state[1]
1101 old = self._state[1]
1101 new = state[1]
1102 new = state[1]
1102 wcrev = self._wcrev()
1103 wcrev = self._wcrev()
1103 if new != wcrev:
1104 if new != wcrev:
1104 dirty = old == wcrev or self._wcchanged()[0]
1105 dirty = old == wcrev or self._wcchanged()[0]
1105 if _updateprompt(self.ui, self, dirty, wcrev, new):
1106 if _updateprompt(self.ui, self, dirty, wcrev, new):
1106 self.get(state, False)
1107 self.get(state, False)
1107
1108
1108 def push(self, opts):
1109 def push(self, opts):
1109 # push is a no-op for SVN
1110 # push is a no-op for SVN
1110 return True
1111 return True
1111
1112
1112 @annotatesubrepoerror
1113 @annotatesubrepoerror
1113 def files(self):
1114 def files(self):
1114 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1115 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1115 doc = xml.dom.minidom.parseString(output)
1116 doc = xml.dom.minidom.parseString(output)
1116 paths = []
1117 paths = []
1117 for e in doc.getElementsByTagName('entry'):
1118 for e in doc.getElementsByTagName('entry'):
1118 kind = str(e.getAttribute('kind'))
1119 kind = str(e.getAttribute('kind'))
1119 if kind != 'file':
1120 if kind != 'file':
1120 continue
1121 continue
1121 name = ''.join(c.data for c
1122 name = ''.join(c.data for c
1122 in e.getElementsByTagName('name')[0].childNodes
1123 in e.getElementsByTagName('name')[0].childNodes
1123 if c.nodeType == c.TEXT_NODE)
1124 if c.nodeType == c.TEXT_NODE)
1124 paths.append(name.encode('utf-8'))
1125 paths.append(name.encode('utf-8'))
1125 return paths
1126 return paths
1126
1127
1127 def filedata(self, name):
1128 def filedata(self, name):
1128 return self._svncommand(['cat'], name)[0]
1129 return self._svncommand(['cat'], name)[0]
1129
1130
1130
1131
1131 class gitsubrepo(abstractsubrepo):
1132 class gitsubrepo(abstractsubrepo):
1132 def __init__(self, ctx, path, state):
1133 def __init__(self, ctx, path, state):
1133 super(gitsubrepo, self).__init__(ctx._repo.ui)
1134 super(gitsubrepo, self).__init__(ctx._repo.ui)
1134 self._state = state
1135 self._state = state
1135 self._ctx = ctx
1136 self._ctx = ctx
1136 self._path = path
1137 self._path = path
1137 self._relpath = os.path.join(reporelpath(ctx._repo), path)
1138 self._relpath = os.path.join(reporelpath(ctx._repo), path)
1138 self._abspath = ctx._repo.wjoin(path)
1139 self._abspath = ctx._repo.wjoin(path)
1139 self._subparent = ctx._repo
1140 self._subparent = ctx._repo
1140 self._ensuregit()
1141 self._ensuregit()
1141
1142
1142 def _ensuregit(self):
1143 def _ensuregit(self):
1143 try:
1144 try:
1144 self._gitexecutable = 'git'
1145 self._gitexecutable = 'git'
1145 out, err = self._gitnodir(['--version'])
1146 out, err = self._gitnodir(['--version'])
1146 except OSError, e:
1147 except OSError, e:
1147 if e.errno != 2 or os.name != 'nt':
1148 if e.errno != 2 or os.name != 'nt':
1148 raise
1149 raise
1149 self._gitexecutable = 'git.cmd'
1150 self._gitexecutable = 'git.cmd'
1150 out, err = self._gitnodir(['--version'])
1151 out, err = self._gitnodir(['--version'])
1151 versionstatus = self._checkversion(out)
1152 versionstatus = self._checkversion(out)
1152 if versionstatus == 'unknown':
1153 if versionstatus == 'unknown':
1153 self.ui.warn(_('cannot retrieve git version\n'))
1154 self.ui.warn(_('cannot retrieve git version\n'))
1154 elif versionstatus == 'abort':
1155 elif versionstatus == 'abort':
1155 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1156 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
1156 elif versionstatus == 'warning':
1157 elif versionstatus == 'warning':
1157 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1158 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1158
1159
1159 @staticmethod
1160 @staticmethod
1160 def _gitversion(out):
1161 def _gitversion(out):
1161 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1162 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1162 if m:
1163 if m:
1163 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1164 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1164
1165
1165 m = re.search(r'^git version (\d+)\.(\d+)', out)
1166 m = re.search(r'^git version (\d+)\.(\d+)', out)
1166 if m:
1167 if m:
1167 return (int(m.group(1)), int(m.group(2)), 0)
1168 return (int(m.group(1)), int(m.group(2)), 0)
1168
1169
1169 return -1
1170 return -1
1170
1171
1171 @staticmethod
1172 @staticmethod
1172 def _checkversion(out):
1173 def _checkversion(out):
1173 '''ensure git version is new enough
1174 '''ensure git version is new enough
1174
1175
1175 >>> _checkversion = gitsubrepo._checkversion
1176 >>> _checkversion = gitsubrepo._checkversion
1176 >>> _checkversion('git version 1.6.0')
1177 >>> _checkversion('git version 1.6.0')
1177 'ok'
1178 'ok'
1178 >>> _checkversion('git version 1.8.5')
1179 >>> _checkversion('git version 1.8.5')
1179 'ok'
1180 'ok'
1180 >>> _checkversion('git version 1.4.0')
1181 >>> _checkversion('git version 1.4.0')
1181 'abort'
1182 'abort'
1182 >>> _checkversion('git version 1.5.0')
1183 >>> _checkversion('git version 1.5.0')
1183 'warning'
1184 'warning'
1184 >>> _checkversion('git version 1.9-rc0')
1185 >>> _checkversion('git version 1.9-rc0')
1185 'ok'
1186 'ok'
1186 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1187 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1187 'ok'
1188 'ok'
1188 >>> _checkversion('git version 1.9.0.GIT')
1189 >>> _checkversion('git version 1.9.0.GIT')
1189 'ok'
1190 'ok'
1190 >>> _checkversion('git version 12345')
1191 >>> _checkversion('git version 12345')
1191 'unknown'
1192 'unknown'
1192 >>> _checkversion('no')
1193 >>> _checkversion('no')
1193 'unknown'
1194 'unknown'
1194 '''
1195 '''
1195 version = gitsubrepo._gitversion(out)
1196 version = gitsubrepo._gitversion(out)
1196 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1197 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1197 # despite the docstring comment. For now, error on 1.4.0, warn on
1198 # despite the docstring comment. For now, error on 1.4.0, warn on
1198 # 1.5.0 but attempt to continue.
1199 # 1.5.0 but attempt to continue.
1199 if version == -1:
1200 if version == -1:
1200 return 'unknown'
1201 return 'unknown'
1201 if version < (1, 5, 0):
1202 if version < (1, 5, 0):
1202 return 'abort'
1203 return 'abort'
1203 elif version < (1, 6, 0):
1204 elif version < (1, 6, 0):
1204 return 'warning'
1205 return 'warning'
1205 return 'ok'
1206 return 'ok'
1206
1207
1207 def _gitcommand(self, commands, env=None, stream=False):
1208 def _gitcommand(self, commands, env=None, stream=False):
1208 return self._gitdir(commands, env=env, stream=stream)[0]
1209 return self._gitdir(commands, env=env, stream=stream)[0]
1209
1210
1210 def _gitdir(self, commands, env=None, stream=False):
1211 def _gitdir(self, commands, env=None, stream=False):
1211 return self._gitnodir(commands, env=env, stream=stream,
1212 return self._gitnodir(commands, env=env, stream=stream,
1212 cwd=self._abspath)
1213 cwd=self._abspath)
1213
1214
1214 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1215 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1215 """Calls the git command
1216 """Calls the git command
1216
1217
1217 The methods tries to call the git command. versions prior to 1.6.0
1218 The methods tries to call the git command. versions prior to 1.6.0
1218 are not supported and very probably fail.
1219 are not supported and very probably fail.
1219 """
1220 """
1220 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1221 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1221 # unless ui.quiet is set, print git's stderr,
1222 # unless ui.quiet is set, print git's stderr,
1222 # which is mostly progress and useful info
1223 # which is mostly progress and useful info
1223 errpipe = None
1224 errpipe = None
1224 if self.ui.quiet:
1225 if self.ui.quiet:
1225 errpipe = open(os.devnull, 'w')
1226 errpipe = open(os.devnull, 'w')
1226 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1227 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1227 cwd=cwd, env=env, close_fds=util.closefds,
1228 cwd=cwd, env=env, close_fds=util.closefds,
1228 stdout=subprocess.PIPE, stderr=errpipe)
1229 stdout=subprocess.PIPE, stderr=errpipe)
1229 if stream:
1230 if stream:
1230 return p.stdout, None
1231 return p.stdout, None
1231
1232
1232 retdata = p.stdout.read().strip()
1233 retdata = p.stdout.read().strip()
1233 # wait for the child to exit to avoid race condition.
1234 # wait for the child to exit to avoid race condition.
1234 p.wait()
1235 p.wait()
1235
1236
1236 if p.returncode != 0 and p.returncode != 1:
1237 if p.returncode != 0 and p.returncode != 1:
1237 # there are certain error codes that are ok
1238 # there are certain error codes that are ok
1238 command = commands[0]
1239 command = commands[0]
1239 if command in ('cat-file', 'symbolic-ref'):
1240 if command in ('cat-file', 'symbolic-ref'):
1240 return retdata, p.returncode
1241 return retdata, p.returncode
1241 # for all others, abort
1242 # for all others, abort
1242 raise util.Abort('git %s error %d in %s' %
1243 raise util.Abort('git %s error %d in %s' %
1243 (command, p.returncode, self._relpath))
1244 (command, p.returncode, self._relpath))
1244
1245
1245 return retdata, p.returncode
1246 return retdata, p.returncode
1246
1247
1247 def _gitmissing(self):
1248 def _gitmissing(self):
1248 return not os.path.exists(os.path.join(self._abspath, '.git'))
1249 return not os.path.exists(os.path.join(self._abspath, '.git'))
1249
1250
1250 def _gitstate(self):
1251 def _gitstate(self):
1251 return self._gitcommand(['rev-parse', 'HEAD'])
1252 return self._gitcommand(['rev-parse', 'HEAD'])
1252
1253
1253 def _gitcurrentbranch(self):
1254 def _gitcurrentbranch(self):
1254 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1255 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1255 if err:
1256 if err:
1256 current = None
1257 current = None
1257 return current
1258 return current
1258
1259
1259 def _gitremote(self, remote):
1260 def _gitremote(self, remote):
1260 out = self._gitcommand(['remote', 'show', '-n', remote])
1261 out = self._gitcommand(['remote', 'show', '-n', remote])
1261 line = out.split('\n')[1]
1262 line = out.split('\n')[1]
1262 i = line.index('URL: ') + len('URL: ')
1263 i = line.index('URL: ') + len('URL: ')
1263 return line[i:]
1264 return line[i:]
1264
1265
1265 def _githavelocally(self, revision):
1266 def _githavelocally(self, revision):
1266 out, code = self._gitdir(['cat-file', '-e', revision])
1267 out, code = self._gitdir(['cat-file', '-e', revision])
1267 return code == 0
1268 return code == 0
1268
1269
1269 def _gitisancestor(self, r1, r2):
1270 def _gitisancestor(self, r1, r2):
1270 base = self._gitcommand(['merge-base', r1, r2])
1271 base = self._gitcommand(['merge-base', r1, r2])
1271 return base == r1
1272 return base == r1
1272
1273
1273 def _gitisbare(self):
1274 def _gitisbare(self):
1274 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1275 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1275
1276
1276 def _gitupdatestat(self):
1277 def _gitupdatestat(self):
1277 """This must be run before git diff-index.
1278 """This must be run before git diff-index.
1278 diff-index only looks at changes to file stat;
1279 diff-index only looks at changes to file stat;
1279 this command looks at file contents and updates the stat."""
1280 this command looks at file contents and updates the stat."""
1280 self._gitcommand(['update-index', '-q', '--refresh'])
1281 self._gitcommand(['update-index', '-q', '--refresh'])
1281
1282
1282 def _gitbranchmap(self):
1283 def _gitbranchmap(self):
1283 '''returns 2 things:
1284 '''returns 2 things:
1284 a map from git branch to revision
1285 a map from git branch to revision
1285 a map from revision to branches'''
1286 a map from revision to branches'''
1286 branch2rev = {}
1287 branch2rev = {}
1287 rev2branch = {}
1288 rev2branch = {}
1288
1289
1289 out = self._gitcommand(['for-each-ref', '--format',
1290 out = self._gitcommand(['for-each-ref', '--format',
1290 '%(objectname) %(refname)'])
1291 '%(objectname) %(refname)'])
1291 for line in out.split('\n'):
1292 for line in out.split('\n'):
1292 revision, ref = line.split(' ')
1293 revision, ref = line.split(' ')
1293 if (not ref.startswith('refs/heads/') and
1294 if (not ref.startswith('refs/heads/') and
1294 not ref.startswith('refs/remotes/')):
1295 not ref.startswith('refs/remotes/')):
1295 continue
1296 continue
1296 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1297 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1297 continue # ignore remote/HEAD redirects
1298 continue # ignore remote/HEAD redirects
1298 branch2rev[ref] = revision
1299 branch2rev[ref] = revision
1299 rev2branch.setdefault(revision, []).append(ref)
1300 rev2branch.setdefault(revision, []).append(ref)
1300 return branch2rev, rev2branch
1301 return branch2rev, rev2branch
1301
1302
1302 def _gittracking(self, branches):
1303 def _gittracking(self, branches):
1303 'return map of remote branch to local tracking branch'
1304 'return map of remote branch to local tracking branch'
1304 # assumes no more than one local tracking branch for each remote
1305 # assumes no more than one local tracking branch for each remote
1305 tracking = {}
1306 tracking = {}
1306 for b in branches:
1307 for b in branches:
1307 if b.startswith('refs/remotes/'):
1308 if b.startswith('refs/remotes/'):
1308 continue
1309 continue
1309 bname = b.split('/', 2)[2]
1310 bname = b.split('/', 2)[2]
1310 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1311 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1311 if remote:
1312 if remote:
1312 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1313 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1313 tracking['refs/remotes/%s/%s' %
1314 tracking['refs/remotes/%s/%s' %
1314 (remote, ref.split('/', 2)[2])] = b
1315 (remote, ref.split('/', 2)[2])] = b
1315 return tracking
1316 return tracking
1316
1317
1317 def _abssource(self, source):
1318 def _abssource(self, source):
1318 if '://' not in source:
1319 if '://' not in source:
1319 # recognize the scp syntax as an absolute source
1320 # recognize the scp syntax as an absolute source
1320 colon = source.find(':')
1321 colon = source.find(':')
1321 if colon != -1 and '/' not in source[:colon]:
1322 if colon != -1 and '/' not in source[:colon]:
1322 return source
1323 return source
1323 self._subsource = source
1324 self._subsource = source
1324 return _abssource(self)
1325 return _abssource(self)
1325
1326
1326 def _fetch(self, source, revision):
1327 def _fetch(self, source, revision):
1327 if self._gitmissing():
1328 if self._gitmissing():
1328 source = self._abssource(source)
1329 source = self._abssource(source)
1329 self.ui.status(_('cloning subrepo %s from %s\n') %
1330 self.ui.status(_('cloning subrepo %s from %s\n') %
1330 (self._relpath, source))
1331 (self._relpath, source))
1331 self._gitnodir(['clone', source, self._abspath])
1332 self._gitnodir(['clone', source, self._abspath])
1332 if self._githavelocally(revision):
1333 if self._githavelocally(revision):
1333 return
1334 return
1334 self.ui.status(_('pulling subrepo %s from %s\n') %
1335 self.ui.status(_('pulling subrepo %s from %s\n') %
1335 (self._relpath, self._gitremote('origin')))
1336 (self._relpath, self._gitremote('origin')))
1336 # try only origin: the originally cloned repo
1337 # try only origin: the originally cloned repo
1337 self._gitcommand(['fetch'])
1338 self._gitcommand(['fetch'])
1338 if not self._githavelocally(revision):
1339 if not self._githavelocally(revision):
1339 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1340 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1340 (revision, self._relpath))
1341 (revision, self._relpath))
1341
1342
1342 @annotatesubrepoerror
1343 @annotatesubrepoerror
1343 def dirty(self, ignoreupdate=False):
1344 def dirty(self, ignoreupdate=False):
1344 if self._gitmissing():
1345 if self._gitmissing():
1345 return self._state[1] != ''
1346 return self._state[1] != ''
1346 if self._gitisbare():
1347 if self._gitisbare():
1347 return True
1348 return True
1348 if not ignoreupdate and self._state[1] != self._gitstate():
1349 if not ignoreupdate and self._state[1] != self._gitstate():
1349 # different version checked out
1350 # different version checked out
1350 return True
1351 return True
1351 # check for staged changes or modified files; ignore untracked files
1352 # check for staged changes or modified files; ignore untracked files
1352 self._gitupdatestat()
1353 self._gitupdatestat()
1353 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1354 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1354 return code == 1
1355 return code == 1
1355
1356
1356 def basestate(self):
1357 def basestate(self):
1357 return self._gitstate()
1358 return self._gitstate()
1358
1359
1359 @annotatesubrepoerror
1360 @annotatesubrepoerror
1360 def get(self, state, overwrite=False):
1361 def get(self, state, overwrite=False):
1361 source, revision, kind = state
1362 source, revision, kind = state
1362 if not revision:
1363 if not revision:
1363 self.remove()
1364 self.remove()
1364 return
1365 return
1365 self._fetch(source, revision)
1366 self._fetch(source, revision)
1366 # if the repo was set to be bare, unbare it
1367 # if the repo was set to be bare, unbare it
1367 if self._gitisbare():
1368 if self._gitisbare():
1368 self._gitcommand(['config', 'core.bare', 'false'])
1369 self._gitcommand(['config', 'core.bare', 'false'])
1369 if self._gitstate() == revision:
1370 if self._gitstate() == revision:
1370 self._gitcommand(['reset', '--hard', 'HEAD'])
1371 self._gitcommand(['reset', '--hard', 'HEAD'])
1371 return
1372 return
1372 elif self._gitstate() == revision:
1373 elif self._gitstate() == revision:
1373 if overwrite:
1374 if overwrite:
1374 # first reset the index to unmark new files for commit, because
1375 # first reset the index to unmark new files for commit, because
1375 # reset --hard will otherwise throw away files added for commit,
1376 # reset --hard will otherwise throw away files added for commit,
1376 # not just unmark them.
1377 # not just unmark them.
1377 self._gitcommand(['reset', 'HEAD'])
1378 self._gitcommand(['reset', 'HEAD'])
1378 self._gitcommand(['reset', '--hard', 'HEAD'])
1379 self._gitcommand(['reset', '--hard', 'HEAD'])
1379 return
1380 return
1380 branch2rev, rev2branch = self._gitbranchmap()
1381 branch2rev, rev2branch = self._gitbranchmap()
1381
1382
1382 def checkout(args):
1383 def checkout(args):
1383 cmd = ['checkout']
1384 cmd = ['checkout']
1384 if overwrite:
1385 if overwrite:
1385 # first reset the index to unmark new files for commit, because
1386 # first reset the index to unmark new files for commit, because
1386 # the -f option will otherwise throw away files added for
1387 # the -f option will otherwise throw away files added for
1387 # commit, not just unmark them.
1388 # commit, not just unmark them.
1388 self._gitcommand(['reset', 'HEAD'])
1389 self._gitcommand(['reset', 'HEAD'])
1389 cmd.append('-f')
1390 cmd.append('-f')
1390 self._gitcommand(cmd + args)
1391 self._gitcommand(cmd + args)
1391 _sanitize(self.ui, self._abspath, '.git')
1392 _sanitize(self.ui, self._abspath, '.git')
1392
1393
1393 def rawcheckout():
1394 def rawcheckout():
1394 # no branch to checkout, check it out with no branch
1395 # no branch to checkout, check it out with no branch
1395 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1396 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1396 self._relpath)
1397 self._relpath)
1397 self.ui.warn(_('check out a git branch if you intend '
1398 self.ui.warn(_('check out a git branch if you intend '
1398 'to make changes\n'))
1399 'to make changes\n'))
1399 checkout(['-q', revision])
1400 checkout(['-q', revision])
1400
1401
1401 if revision not in rev2branch:
1402 if revision not in rev2branch:
1402 rawcheckout()
1403 rawcheckout()
1403 return
1404 return
1404 branches = rev2branch[revision]
1405 branches = rev2branch[revision]
1405 firstlocalbranch = None
1406 firstlocalbranch = None
1406 for b in branches:
1407 for b in branches:
1407 if b == 'refs/heads/master':
1408 if b == 'refs/heads/master':
1408 # master trumps all other branches
1409 # master trumps all other branches
1409 checkout(['refs/heads/master'])
1410 checkout(['refs/heads/master'])
1410 return
1411 return
1411 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1412 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1412 firstlocalbranch = b
1413 firstlocalbranch = b
1413 if firstlocalbranch:
1414 if firstlocalbranch:
1414 checkout([firstlocalbranch])
1415 checkout([firstlocalbranch])
1415 return
1416 return
1416
1417
1417 tracking = self._gittracking(branch2rev.keys())
1418 tracking = self._gittracking(branch2rev.keys())
1418 # choose a remote branch already tracked if possible
1419 # choose a remote branch already tracked if possible
1419 remote = branches[0]
1420 remote = branches[0]
1420 if remote not in tracking:
1421 if remote not in tracking:
1421 for b in branches:
1422 for b in branches:
1422 if b in tracking:
1423 if b in tracking:
1423 remote = b
1424 remote = b
1424 break
1425 break
1425
1426
1426 if remote not in tracking:
1427 if remote not in tracking:
1427 # create a new local tracking branch
1428 # create a new local tracking branch
1428 local = remote.split('/', 3)[3]
1429 local = remote.split('/', 3)[3]
1429 checkout(['-b', local, remote])
1430 checkout(['-b', local, remote])
1430 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1431 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1431 # When updating to a tracked remote branch,
1432 # When updating to a tracked remote branch,
1432 # if the local tracking branch is downstream of it,
1433 # if the local tracking branch is downstream of it,
1433 # a normal `git pull` would have performed a "fast-forward merge"
1434 # a normal `git pull` would have performed a "fast-forward merge"
1434 # which is equivalent to updating the local branch to the remote.
1435 # which is equivalent to updating the local branch to the remote.
1435 # Since we are only looking at branching at update, we need to
1436 # Since we are only looking at branching at update, we need to
1436 # detect this situation and perform this action lazily.
1437 # detect this situation and perform this action lazily.
1437 if tracking[remote] != self._gitcurrentbranch():
1438 if tracking[remote] != self._gitcurrentbranch():
1438 checkout([tracking[remote]])
1439 checkout([tracking[remote]])
1439 self._gitcommand(['merge', '--ff', remote])
1440 self._gitcommand(['merge', '--ff', remote])
1440 _sanitize(self.ui, self._abspath, '.git')
1441 _sanitize(self.ui, self._abspath, '.git')
1441 else:
1442 else:
1442 # a real merge would be required, just checkout the revision
1443 # a real merge would be required, just checkout the revision
1443 rawcheckout()
1444 rawcheckout()
1444
1445
1445 @annotatesubrepoerror
1446 @annotatesubrepoerror
1446 def commit(self, text, user, date):
1447 def commit(self, text, user, date):
1447 if self._gitmissing():
1448 if self._gitmissing():
1448 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1449 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1449 cmd = ['commit', '-a', '-m', text]
1450 cmd = ['commit', '-a', '-m', text]
1450 env = os.environ.copy()
1451 env = os.environ.copy()
1451 if user:
1452 if user:
1452 cmd += ['--author', user]
1453 cmd += ['--author', user]
1453 if date:
1454 if date:
1454 # git's date parser silently ignores when seconds < 1e9
1455 # git's date parser silently ignores when seconds < 1e9
1455 # convert to ISO8601
1456 # convert to ISO8601
1456 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1457 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1457 '%Y-%m-%dT%H:%M:%S %1%2')
1458 '%Y-%m-%dT%H:%M:%S %1%2')
1458 self._gitcommand(cmd, env=env)
1459 self._gitcommand(cmd, env=env)
1459 # make sure commit works otherwise HEAD might not exist under certain
1460 # make sure commit works otherwise HEAD might not exist under certain
1460 # circumstances
1461 # circumstances
1461 return self._gitstate()
1462 return self._gitstate()
1462
1463
1463 @annotatesubrepoerror
1464 @annotatesubrepoerror
1464 def merge(self, state):
1465 def merge(self, state):
1465 source, revision, kind = state
1466 source, revision, kind = state
1466 self._fetch(source, revision)
1467 self._fetch(source, revision)
1467 base = self._gitcommand(['merge-base', revision, self._state[1]])
1468 base = self._gitcommand(['merge-base', revision, self._state[1]])
1468 self._gitupdatestat()
1469 self._gitupdatestat()
1469 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1470 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1470
1471
1471 def mergefunc():
1472 def mergefunc():
1472 if base == revision:
1473 if base == revision:
1473 self.get(state) # fast forward merge
1474 self.get(state) # fast forward merge
1474 elif base != self._state[1]:
1475 elif base != self._state[1]:
1475 self._gitcommand(['merge', '--no-commit', revision])
1476 self._gitcommand(['merge', '--no-commit', revision])
1476 _sanitize(self.ui, self._abspath, '.git')
1477 _sanitize(self.ui, self._abspath, '.git')
1477
1478
1478 if self.dirty():
1479 if self.dirty():
1479 if self._gitstate() != revision:
1480 if self._gitstate() != revision:
1480 dirty = self._gitstate() == self._state[1] or code != 0
1481 dirty = self._gitstate() == self._state[1] or code != 0
1481 if _updateprompt(self.ui, self, dirty,
1482 if _updateprompt(self.ui, self, dirty,
1482 self._state[1][:7], revision[:7]):
1483 self._state[1][:7], revision[:7]):
1483 mergefunc()
1484 mergefunc()
1484 else:
1485 else:
1485 mergefunc()
1486 mergefunc()
1486
1487
1487 @annotatesubrepoerror
1488 @annotatesubrepoerror
1488 def push(self, opts):
1489 def push(self, opts):
1489 force = opts.get('force')
1490 force = opts.get('force')
1490
1491
1491 if not self._state[1]:
1492 if not self._state[1]:
1492 return True
1493 return True
1493 if self._gitmissing():
1494 if self._gitmissing():
1494 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1495 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1495 # if a branch in origin contains the revision, nothing to do
1496 # if a branch in origin contains the revision, nothing to do
1496 branch2rev, rev2branch = self._gitbranchmap()
1497 branch2rev, rev2branch = self._gitbranchmap()
1497 if self._state[1] in rev2branch:
1498 if self._state[1] in rev2branch:
1498 for b in rev2branch[self._state[1]]:
1499 for b in rev2branch[self._state[1]]:
1499 if b.startswith('refs/remotes/origin/'):
1500 if b.startswith('refs/remotes/origin/'):
1500 return True
1501 return True
1501 for b, revision in branch2rev.iteritems():
1502 for b, revision in branch2rev.iteritems():
1502 if b.startswith('refs/remotes/origin/'):
1503 if b.startswith('refs/remotes/origin/'):
1503 if self._gitisancestor(self._state[1], revision):
1504 if self._gitisancestor(self._state[1], revision):
1504 return True
1505 return True
1505 # otherwise, try to push the currently checked out branch
1506 # otherwise, try to push the currently checked out branch
1506 cmd = ['push']
1507 cmd = ['push']
1507 if force:
1508 if force:
1508 cmd.append('--force')
1509 cmd.append('--force')
1509
1510
1510 current = self._gitcurrentbranch()
1511 current = self._gitcurrentbranch()
1511 if current:
1512 if current:
1512 # determine if the current branch is even useful
1513 # determine if the current branch is even useful
1513 if not self._gitisancestor(self._state[1], current):
1514 if not self._gitisancestor(self._state[1], current):
1514 self.ui.warn(_('unrelated git branch checked out '
1515 self.ui.warn(_('unrelated git branch checked out '
1515 'in subrepo %s\n') % self._relpath)
1516 'in subrepo %s\n') % self._relpath)
1516 return False
1517 return False
1517 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1518 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1518 (current.split('/', 2)[2], self._relpath))
1519 (current.split('/', 2)[2], self._relpath))
1519 ret = self._gitdir(cmd + ['origin', current])
1520 ret = self._gitdir(cmd + ['origin', current])
1520 return ret[1] == 0
1521 return ret[1] == 0
1521 else:
1522 else:
1522 self.ui.warn(_('no branch checked out in subrepo %s\n'
1523 self.ui.warn(_('no branch checked out in subrepo %s\n'
1523 'cannot push revision %s\n') %
1524 'cannot push revision %s\n') %
1524 (self._relpath, self._state[1]))
1525 (self._relpath, self._state[1]))
1525 return False
1526 return False
1526
1527
1527 @annotatesubrepoerror
1528 @annotatesubrepoerror
1528 def remove(self):
1529 def remove(self):
1529 if self._gitmissing():
1530 if self._gitmissing():
1530 return
1531 return
1531 if self.dirty():
1532 if self.dirty():
1532 self.ui.warn(_('not removing repo %s because '
1533 self.ui.warn(_('not removing repo %s because '
1533 'it has changes.\n') % self._relpath)
1534 'it has changes.\n') % self._relpath)
1534 return
1535 return
1535 # we can't fully delete the repository as it may contain
1536 # we can't fully delete the repository as it may contain
1536 # local-only history
1537 # local-only history
1537 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1538 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1538 self._gitcommand(['config', 'core.bare', 'true'])
1539 self._gitcommand(['config', 'core.bare', 'true'])
1539 for f in os.listdir(self._abspath):
1540 for f in os.listdir(self._abspath):
1540 if f == '.git':
1541 if f == '.git':
1541 continue
1542 continue
1542 path = os.path.join(self._abspath, f)
1543 path = os.path.join(self._abspath, f)
1543 if os.path.isdir(path) and not os.path.islink(path):
1544 if os.path.isdir(path) and not os.path.islink(path):
1544 shutil.rmtree(path)
1545 shutil.rmtree(path)
1545 else:
1546 else:
1546 os.remove(path)
1547 os.remove(path)
1547
1548
1548 def archive(self, archiver, prefix, match=None):
1549 def archive(self, archiver, prefix, match=None):
1549 total = 0
1550 total = 0
1550 source, revision = self._state
1551 source, revision = self._state
1551 if not revision:
1552 if not revision:
1552 return total
1553 return total
1553 self._fetch(source, revision)
1554 self._fetch(source, revision)
1554
1555
1555 # Parse git's native archive command.
1556 # Parse git's native archive command.
1556 # This should be much faster than manually traversing the trees
1557 # This should be much faster than manually traversing the trees
1557 # and objects with many subprocess calls.
1558 # and objects with many subprocess calls.
1558 tarstream = self._gitcommand(['archive', revision], stream=True)
1559 tarstream = self._gitcommand(['archive', revision], stream=True)
1559 tar = tarfile.open(fileobj=tarstream, mode='r|')
1560 tar = tarfile.open(fileobj=tarstream, mode='r|')
1560 relpath = subrelpath(self)
1561 relpath = subrelpath(self)
1561 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1562 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1562 for i, info in enumerate(tar):
1563 for i, info in enumerate(tar):
1563 if info.isdir():
1564 if info.isdir():
1564 continue
1565 continue
1565 if match and not match(info.name):
1566 if match and not match(info.name):
1566 continue
1567 continue
1567 if info.issym():
1568 if info.issym():
1568 data = info.linkname
1569 data = info.linkname
1569 else:
1570 else:
1570 data = tar.extractfile(info).read()
1571 data = tar.extractfile(info).read()
1571 archiver.addfile(os.path.join(prefix, self._path, info.name),
1572 archiver.addfile(os.path.join(prefix, self._path, info.name),
1572 info.mode, info.issym(), data)
1573 info.mode, info.issym(), data)
1573 total += 1
1574 total += 1
1574 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1575 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1575 unit=_('files'))
1576 unit=_('files'))
1576 self.ui.progress(_('archiving (%s)') % relpath, None)
1577 self.ui.progress(_('archiving (%s)') % relpath, None)
1577 return total
1578 return total
1578
1579
1579
1580
1580 @annotatesubrepoerror
1581 @annotatesubrepoerror
1581 def cat(self, match, prefix, **opts):
1582 def cat(self, match, prefix, **opts):
1582 rev = self._state[1]
1583 rev = self._state[1]
1583 if match.anypats():
1584 if match.anypats():
1584 return 1 #No support for include/exclude yet
1585 return 1 #No support for include/exclude yet
1585
1586
1586 if not match.files():
1587 if not match.files():
1587 return 1
1588 return 1
1588
1589
1589 for f in match.files():
1590 for f in match.files():
1590 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1591 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1591 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1592 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1592 self._ctx.node(),
1593 self._ctx.node(),
1593 pathname=os.path.join(prefix, f))
1594 pathname=os.path.join(prefix, f))
1594 fp.write(output)
1595 fp.write(output)
1595 fp.close()
1596 fp.close()
1596 return 0
1597 return 0
1597
1598
1598
1599
1599 @annotatesubrepoerror
1600 @annotatesubrepoerror
1600 def status(self, rev2, **opts):
1601 def status(self, rev2, **opts):
1601 rev1 = self._state[1]
1602 rev1 = self._state[1]
1602 if self._gitmissing() or not rev1:
1603 if self._gitmissing() or not rev1:
1603 # if the repo is missing, return no results
1604 # if the repo is missing, return no results
1604 return [], [], [], [], [], [], []
1605 return [], [], [], [], [], [], []
1605 modified, added, removed = [], [], []
1606 modified, added, removed = [], [], []
1606 self._gitupdatestat()
1607 self._gitupdatestat()
1607 if rev2:
1608 if rev2:
1608 command = ['diff-tree', rev1, rev2]
1609 command = ['diff-tree', rev1, rev2]
1609 else:
1610 else:
1610 command = ['diff-index', rev1]
1611 command = ['diff-index', rev1]
1611 out = self._gitcommand(command)
1612 out = self._gitcommand(command)
1612 for line in out.split('\n'):
1613 for line in out.split('\n'):
1613 tab = line.find('\t')
1614 tab = line.find('\t')
1614 if tab == -1:
1615 if tab == -1:
1615 continue
1616 continue
1616 status, f = line[tab - 1], line[tab + 1:]
1617 status, f = line[tab - 1], line[tab + 1:]
1617 if status == 'M':
1618 if status == 'M':
1618 modified.append(f)
1619 modified.append(f)
1619 elif status == 'A':
1620 elif status == 'A':
1620 added.append(f)
1621 added.append(f)
1621 elif status == 'D':
1622 elif status == 'D':
1622 removed.append(f)
1623 removed.append(f)
1623
1624
1624 deleted, unknown, ignored, clean = [], [], [], []
1625 deleted, unknown, ignored, clean = [], [], [], []
1625
1626
1626 if not rev2:
1627 if not rev2:
1627 command = ['ls-files', '--others', '--exclude-standard']
1628 command = ['ls-files', '--others', '--exclude-standard']
1628 out = self._gitcommand(command)
1629 out = self._gitcommand(command)
1629 for line in out.split('\n'):
1630 for line in out.split('\n'):
1630 if len(line) == 0:
1631 if len(line) == 0:
1631 continue
1632 continue
1632 unknown.append(line)
1633 unknown.append(line)
1633
1634
1634 return scmutil.status(modified, added, removed, deleted,
1635 return scmutil.status(modified, added, removed, deleted,
1635 unknown, ignored, clean)
1636 unknown, ignored, clean)
1636
1637
1637 @annotatesubrepoerror
1638 @annotatesubrepoerror
1638 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1639 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1639 node1 = self._state[1]
1640 node1 = self._state[1]
1640 cmd = ['diff']
1641 cmd = ['diff']
1641 if opts['stat']:
1642 if opts['stat']:
1642 cmd.append('--stat')
1643 cmd.append('--stat')
1643 else:
1644 else:
1644 # for Git, this also implies '-p'
1645 # for Git, this also implies '-p'
1645 cmd.append('-U%d' % diffopts.context)
1646 cmd.append('-U%d' % diffopts.context)
1646
1647
1647 gitprefix = os.path.join(prefix, self._path)
1648 gitprefix = os.path.join(prefix, self._path)
1648
1649
1649 if diffopts.noprefix:
1650 if diffopts.noprefix:
1650 cmd.extend(['--src-prefix=%s/' % gitprefix,
1651 cmd.extend(['--src-prefix=%s/' % gitprefix,
1651 '--dst-prefix=%s/' % gitprefix])
1652 '--dst-prefix=%s/' % gitprefix])
1652 else:
1653 else:
1653 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1654 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1654 '--dst-prefix=b/%s/' % gitprefix])
1655 '--dst-prefix=b/%s/' % gitprefix])
1655
1656
1656 if diffopts.ignorews:
1657 if diffopts.ignorews:
1657 cmd.append('--ignore-all-space')
1658 cmd.append('--ignore-all-space')
1658 if diffopts.ignorewsamount:
1659 if diffopts.ignorewsamount:
1659 cmd.append('--ignore-space-change')
1660 cmd.append('--ignore-space-change')
1660 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1661 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1661 and diffopts.ignoreblanklines:
1662 and diffopts.ignoreblanklines:
1662 cmd.append('--ignore-blank-lines')
1663 cmd.append('--ignore-blank-lines')
1663
1664
1664 cmd.append(node1)
1665 cmd.append(node1)
1665 if node2:
1666 if node2:
1666 cmd.append(node2)
1667 cmd.append(node2)
1667
1668
1668 if match.anypats():
1669 if match.anypats():
1669 return #No support for include/exclude yet
1670 return #No support for include/exclude yet
1670
1671
1671 output = ""
1672 output = ""
1672 if match.always():
1673 if match.always():
1673 output += self._gitcommand(cmd) + '\n'
1674 output += self._gitcommand(cmd) + '\n'
1674 elif match.files():
1675 elif match.files():
1675 for f in match.files():
1676 for f in match.files():
1676 output += self._gitcommand(cmd + [f]) + '\n'
1677 output += self._gitcommand(cmd + [f]) + '\n'
1677 elif match(gitprefix): #Subrepo is matched
1678 elif match(gitprefix): #Subrepo is matched
1678 output += self._gitcommand(cmd) + '\n'
1679 output += self._gitcommand(cmd) + '\n'
1679
1680
1680 if output.strip():
1681 if output.strip():
1681 ui.write(output)
1682 ui.write(output)
1682
1683
1683 @annotatesubrepoerror
1684 @annotatesubrepoerror
1684 def revert(self, substate, *pats, **opts):
1685 def revert(self, substate, *pats, **opts):
1685 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1686 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1686 if not opts.get('no_backup'):
1687 if not opts.get('no_backup'):
1687 status = self.status(None)
1688 status = self.status(None)
1688 names = status.modified
1689 names = status.modified
1689 for name in names:
1690 for name in names:
1690 bakname = "%s.orig" % name
1691 bakname = "%s.orig" % name
1691 self.ui.note(_('saving current version of %s as %s\n') %
1692 self.ui.note(_('saving current version of %s as %s\n') %
1692 (name, bakname))
1693 (name, bakname))
1693 util.rename(os.path.join(self._abspath, name),
1694 util.rename(os.path.join(self._abspath, name),
1694 os.path.join(self._abspath, bakname))
1695 os.path.join(self._abspath, bakname))
1695
1696
1696 self.get(substate, overwrite=True)
1697 if not opts.get('dry_run'):
1698 self.get(substate, overwrite=True)
1697 return []
1699 return []
1698
1700
1699 def shortid(self, revid):
1701 def shortid(self, revid):
1700 return revid[:7]
1702 return revid[:7]
1701
1703
1702 types = {
1704 types = {
1703 'hg': hgsubrepo,
1705 'hg': hgsubrepo,
1704 'svn': svnsubrepo,
1706 'svn': svnsubrepo,
1705 'git': gitsubrepo,
1707 'git': gitsubrepo,
1706 }
1708 }
@@ -1,1474 +1,1481 b''
1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
2
2
3 $ echo "[ui]" >> $HGRCPATH
3 $ echo "[ui]" >> $HGRCPATH
4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
5
5
6 $ hg init t
6 $ hg init t
7 $ cd t
7 $ cd t
8
8
9 first revision, no sub
9 first revision, no sub
10
10
11 $ echo a > a
11 $ echo a > a
12 $ hg ci -Am0
12 $ hg ci -Am0
13 adding a
13 adding a
14
14
15 add first sub
15 add first sub
16
16
17 $ echo s = s > .hgsub
17 $ echo s = s > .hgsub
18 $ hg add .hgsub
18 $ hg add .hgsub
19 $ hg init s
19 $ hg init s
20 $ echo a > s/a
20 $ echo a > s/a
21
21
22 Issue2232: committing a subrepo without .hgsub
22 Issue2232: committing a subrepo without .hgsub
23
23
24 $ hg ci -mbad s
24 $ hg ci -mbad s
25 abort: can't commit subrepos without .hgsub
25 abort: can't commit subrepos without .hgsub
26 [255]
26 [255]
27
27
28 $ hg -R s ci -Ams0
28 $ hg -R s ci -Ams0
29 adding a
29 adding a
30 $ hg sum
30 $ hg sum
31 parent: 0:f7b1eb17ad24 tip
31 parent: 0:f7b1eb17ad24 tip
32 0
32 0
33 branch: default
33 branch: default
34 commit: 1 added, 1 subrepos
34 commit: 1 added, 1 subrepos
35 update: (current)
35 update: (current)
36 $ hg ci -m1
36 $ hg ci -m1
37
37
38 test handling .hgsubstate "added" explicitly.
38 test handling .hgsubstate "added" explicitly.
39
39
40 $ hg parents --template '{node}\n{files}\n'
40 $ hg parents --template '{node}\n{files}\n'
41 7cf8cfea66e410e8e3336508dfeec07b3192de51
41 7cf8cfea66e410e8e3336508dfeec07b3192de51
42 .hgsub .hgsubstate
42 .hgsub .hgsubstate
43 $ hg rollback -q
43 $ hg rollback -q
44 $ hg add .hgsubstate
44 $ hg add .hgsubstate
45 $ hg ci -m1
45 $ hg ci -m1
46 $ hg parents --template '{node}\n{files}\n'
46 $ hg parents --template '{node}\n{files}\n'
47 7cf8cfea66e410e8e3336508dfeec07b3192de51
47 7cf8cfea66e410e8e3336508dfeec07b3192de51
48 .hgsub .hgsubstate
48 .hgsub .hgsubstate
49
49
50 Revert subrepo and test subrepo fileset keyword:
50 Revert subrepo and test subrepo fileset keyword:
51
51
52 $ echo b > s/a
52 $ echo b > s/a
53 $ hg revert --dry-run "set:subrepo('glob:s*')"
54 reverting subrepo s
55 reverting s/a (glob)
56 $ cat s/a
57 b
53 $ hg revert "set:subrepo('glob:s*')"
58 $ hg revert "set:subrepo('glob:s*')"
54 reverting subrepo s
59 reverting subrepo s
55 reverting s/a (glob)
60 reverting s/a (glob)
61 $ cat s/a
62 a
56 $ rm s/a.orig
63 $ rm s/a.orig
57
64
58 Revert subrepo with no backup. The "reverting s/a" line is gone since
65 Revert subrepo with no backup. The "reverting s/a" line is gone since
59 we're really running 'hg update' in the subrepo:
66 we're really running 'hg update' in the subrepo:
60
67
61 $ echo b > s/a
68 $ echo b > s/a
62 $ hg revert --no-backup s
69 $ hg revert --no-backup s
63 reverting subrepo s
70 reverting subrepo s
64
71
65 Issue2022: update -C
72 Issue2022: update -C
66
73
67 $ echo b > s/a
74 $ echo b > s/a
68 $ hg sum
75 $ hg sum
69 parent: 1:7cf8cfea66e4 tip
76 parent: 1:7cf8cfea66e4 tip
70 1
77 1
71 branch: default
78 branch: default
72 commit: 1 subrepos
79 commit: 1 subrepos
73 update: (current)
80 update: (current)
74 $ hg co -C 1
81 $ hg co -C 1
75 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 $ hg sum
83 $ hg sum
77 parent: 1:7cf8cfea66e4 tip
84 parent: 1:7cf8cfea66e4 tip
78 1
85 1
79 branch: default
86 branch: default
80 commit: (clean)
87 commit: (clean)
81 update: (current)
88 update: (current)
82
89
83 commands that require a clean repo should respect subrepos
90 commands that require a clean repo should respect subrepos
84
91
85 $ echo b >> s/a
92 $ echo b >> s/a
86 $ hg backout tip
93 $ hg backout tip
87 abort: uncommitted changes in subrepo s
94 abort: uncommitted changes in subrepo s
88 [255]
95 [255]
89 $ hg revert -C -R s s/a
96 $ hg revert -C -R s s/a
90
97
91 add sub sub
98 add sub sub
92
99
93 $ echo ss = ss > s/.hgsub
100 $ echo ss = ss > s/.hgsub
94 $ hg init s/ss
101 $ hg init s/ss
95 $ echo a > s/ss/a
102 $ echo a > s/ss/a
96 $ hg -R s add s/.hgsub
103 $ hg -R s add s/.hgsub
97 $ hg -R s/ss add s/ss/a
104 $ hg -R s/ss add s/ss/a
98 $ hg sum
105 $ hg sum
99 parent: 1:7cf8cfea66e4 tip
106 parent: 1:7cf8cfea66e4 tip
100 1
107 1
101 branch: default
108 branch: default
102 commit: 1 subrepos
109 commit: 1 subrepos
103 update: (current)
110 update: (current)
104 $ hg ci -m2
111 $ hg ci -m2
105 committing subrepository s
112 committing subrepository s
106 committing subrepository s/ss (glob)
113 committing subrepository s/ss (glob)
107 $ hg sum
114 $ hg sum
108 parent: 2:df30734270ae tip
115 parent: 2:df30734270ae tip
109 2
116 2
110 branch: default
117 branch: default
111 commit: (clean)
118 commit: (clean)
112 update: (current)
119 update: (current)
113
120
114 test handling .hgsubstate "modified" explicitly.
121 test handling .hgsubstate "modified" explicitly.
115
122
116 $ hg parents --template '{node}\n{files}\n'
123 $ hg parents --template '{node}\n{files}\n'
117 df30734270ae757feb35e643b7018e818e78a9aa
124 df30734270ae757feb35e643b7018e818e78a9aa
118 .hgsubstate
125 .hgsubstate
119 $ hg rollback -q
126 $ hg rollback -q
120 $ hg status -A .hgsubstate
127 $ hg status -A .hgsubstate
121 M .hgsubstate
128 M .hgsubstate
122 $ hg ci -m2
129 $ hg ci -m2
123 $ hg parents --template '{node}\n{files}\n'
130 $ hg parents --template '{node}\n{files}\n'
124 df30734270ae757feb35e643b7018e818e78a9aa
131 df30734270ae757feb35e643b7018e818e78a9aa
125 .hgsubstate
132 .hgsubstate
126
133
127 bump sub rev (and check it is ignored by ui.commitsubrepos)
134 bump sub rev (and check it is ignored by ui.commitsubrepos)
128
135
129 $ echo b > s/a
136 $ echo b > s/a
130 $ hg -R s ci -ms1
137 $ hg -R s ci -ms1
131 $ hg --config ui.commitsubrepos=no ci -m3
138 $ hg --config ui.commitsubrepos=no ci -m3
132
139
133 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
140 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
134
141
135 $ echo c > s/a
142 $ echo c > s/a
136 $ hg --config ui.commitsubrepos=no ci -m4
143 $ hg --config ui.commitsubrepos=no ci -m4
137 abort: uncommitted changes in subrepo s
144 abort: uncommitted changes in subrepo s
138 (use --subrepos for recursive commit)
145 (use --subrepos for recursive commit)
139 [255]
146 [255]
140 $ hg id
147 $ hg id
141 f6affe3fbfaa+ tip
148 f6affe3fbfaa+ tip
142 $ hg -R s ci -mc
149 $ hg -R s ci -mc
143 $ hg id
150 $ hg id
144 f6affe3fbfaa+ tip
151 f6affe3fbfaa+ tip
145 $ echo d > s/a
152 $ echo d > s/a
146 $ hg ci -m4
153 $ hg ci -m4
147 committing subrepository s
154 committing subrepository s
148 $ hg tip -R s
155 $ hg tip -R s
149 changeset: 4:02dcf1d70411
156 changeset: 4:02dcf1d70411
150 tag: tip
157 tag: tip
151 user: test
158 user: test
152 date: Thu Jan 01 00:00:00 1970 +0000
159 date: Thu Jan 01 00:00:00 1970 +0000
153 summary: 4
160 summary: 4
154
161
155
162
156 check caching
163 check caching
157
164
158 $ hg co 0
165 $ hg co 0
159 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
166 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
160 $ hg debugsub
167 $ hg debugsub
161
168
162 restore
169 restore
163
170
164 $ hg co
171 $ hg co
165 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
172 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
166 $ hg debugsub
173 $ hg debugsub
167 path s
174 path s
168 source s
175 source s
169 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
176 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
170
177
171 new branch for merge tests
178 new branch for merge tests
172
179
173 $ hg co 1
180 $ hg co 1
174 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
181 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
175 $ echo t = t >> .hgsub
182 $ echo t = t >> .hgsub
176 $ hg init t
183 $ hg init t
177 $ echo t > t/t
184 $ echo t > t/t
178 $ hg -R t add t
185 $ hg -R t add t
179 adding t/t (glob)
186 adding t/t (glob)
180
187
181 5
188 5
182
189
183 $ hg ci -m5 # add sub
190 $ hg ci -m5 # add sub
184 committing subrepository t
191 committing subrepository t
185 created new head
192 created new head
186 $ echo t2 > t/t
193 $ echo t2 > t/t
187
194
188 6
195 6
189
196
190 $ hg st -R s
197 $ hg st -R s
191 $ hg ci -m6 # change sub
198 $ hg ci -m6 # change sub
192 committing subrepository t
199 committing subrepository t
193 $ hg debugsub
200 $ hg debugsub
194 path s
201 path s
195 source s
202 source s
196 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
203 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
197 path t
204 path t
198 source t
205 source t
199 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
206 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
200 $ echo t3 > t/t
207 $ echo t3 > t/t
201
208
202 7
209 7
203
210
204 $ hg ci -m7 # change sub again for conflict test
211 $ hg ci -m7 # change sub again for conflict test
205 committing subrepository t
212 committing subrepository t
206 $ hg rm .hgsub
213 $ hg rm .hgsub
207
214
208 8
215 8
209
216
210 $ hg ci -m8 # remove sub
217 $ hg ci -m8 # remove sub
211
218
212 test handling .hgsubstate "removed" explicitly.
219 test handling .hgsubstate "removed" explicitly.
213
220
214 $ hg parents --template '{node}\n{files}\n'
221 $ hg parents --template '{node}\n{files}\n'
215 96615c1dad2dc8e3796d7332c77ce69156f7b78e
222 96615c1dad2dc8e3796d7332c77ce69156f7b78e
216 .hgsub .hgsubstate
223 .hgsub .hgsubstate
217 $ hg rollback -q
224 $ hg rollback -q
218 $ hg remove .hgsubstate
225 $ hg remove .hgsubstate
219 $ hg ci -m8
226 $ hg ci -m8
220 $ hg parents --template '{node}\n{files}\n'
227 $ hg parents --template '{node}\n{files}\n'
221 96615c1dad2dc8e3796d7332c77ce69156f7b78e
228 96615c1dad2dc8e3796d7332c77ce69156f7b78e
222 .hgsub .hgsubstate
229 .hgsub .hgsubstate
223
230
224 merge tests
231 merge tests
225
232
226 $ hg co -C 3
233 $ hg co -C 3
227 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
234 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 $ hg merge 5 # test adding
235 $ hg merge 5 # test adding
229 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
236 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
230 (branch merge, don't forget to commit)
237 (branch merge, don't forget to commit)
231 $ hg debugsub
238 $ hg debugsub
232 path s
239 path s
233 source s
240 source s
234 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
241 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
235 path t
242 path t
236 source t
243 source t
237 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
244 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
238 $ hg ci -m9
245 $ hg ci -m9
239 created new head
246 created new head
240 $ hg merge 6 --debug # test change
247 $ hg merge 6 --debug # test change
241 searching for copies back to rev 2
248 searching for copies back to rev 2
242 resolving manifests
249 resolving manifests
243 branchmerge: True, force: False, partial: False
250 branchmerge: True, force: False, partial: False
244 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
251 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
245 .hgsubstate: versions differ -> m
252 .hgsubstate: versions differ -> m
246 updating: .hgsubstate 1/1 files (100.00%)
253 updating: .hgsubstate 1/1 files (100.00%)
247 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
254 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
248 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
255 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
249 getting subrepo t
256 getting subrepo t
250 resolving manifests
257 resolving manifests
251 branchmerge: False, force: False, partial: False
258 branchmerge: False, force: False, partial: False
252 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
259 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
253 t: remote is newer -> g
260 t: remote is newer -> g
254 getting t
261 getting t
255 updating: t 1/1 files (100.00%)
262 updating: t 1/1 files (100.00%)
256 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
263 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 (branch merge, don't forget to commit)
264 (branch merge, don't forget to commit)
258 $ hg debugsub
265 $ hg debugsub
259 path s
266 path s
260 source s
267 source s
261 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
268 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
262 path t
269 path t
263 source t
270 source t
264 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
271 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
265 $ echo conflict > t/t
272 $ echo conflict > t/t
266 $ hg ci -m10
273 $ hg ci -m10
267 committing subrepository t
274 committing subrepository t
268 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
275 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
269 searching for copies back to rev 2
276 searching for copies back to rev 2
270 resolving manifests
277 resolving manifests
271 branchmerge: True, force: False, partial: False
278 branchmerge: True, force: False, partial: False
272 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
279 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
273 .hgsubstate: versions differ -> m
280 .hgsubstate: versions differ -> m
274 updating: .hgsubstate 1/1 files (100.00%)
281 updating: .hgsubstate 1/1 files (100.00%)
275 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
282 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
276 subrepo t: both sides changed
283 subrepo t: both sides changed
277 subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
284 subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
278 (M)erge, keep (l)ocal or keep (r)emote? m
285 (M)erge, keep (l)ocal or keep (r)emote? m
279 merging subrepo t
286 merging subrepo t
280 searching for copies back to rev 2
287 searching for copies back to rev 2
281 resolving manifests
288 resolving manifests
282 branchmerge: True, force: False, partial: False
289 branchmerge: True, force: False, partial: False
283 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
290 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
284 preserving t for resolve of t
291 preserving t for resolve of t
285 t: versions differ -> m
292 t: versions differ -> m
286 updating: t 1/1 files (100.00%)
293 updating: t 1/1 files (100.00%)
287 picked tool 'internal:merge' for t (binary False symlink False)
294 picked tool 'internal:merge' for t (binary False symlink False)
288 merging t
295 merging t
289 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
296 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
290 warning: conflicts during merge.
297 warning: conflicts during merge.
291 merging t incomplete! (edit conflicts, then use 'hg resolve --mark')
298 merging t incomplete! (edit conflicts, then use 'hg resolve --mark')
292 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
299 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
293 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
300 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
294 subrepo t: merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
301 subrepo t: merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
295 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
302 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 (branch merge, don't forget to commit)
303 (branch merge, don't forget to commit)
297
304
298 should conflict
305 should conflict
299
306
300 $ cat t/t
307 $ cat t/t
301 <<<<<<< local: 20a0db6fbf6c - test: 10
308 <<<<<<< local: 20a0db6fbf6c - test: 10
302 conflict
309 conflict
303 =======
310 =======
304 t3
311 t3
305 >>>>>>> other: 7af322bc1198 - test: 7
312 >>>>>>> other: 7af322bc1198 - test: 7
306
313
307 clone
314 clone
308
315
309 $ cd ..
316 $ cd ..
310 $ hg clone t tc
317 $ hg clone t tc
311 updating to branch default
318 updating to branch default
312 cloning subrepo s from $TESTTMP/t/s
319 cloning subrepo s from $TESTTMP/t/s
313 cloning subrepo s/ss from $TESTTMP/t/s/ss (glob)
320 cloning subrepo s/ss from $TESTTMP/t/s/ss (glob)
314 cloning subrepo t from $TESTTMP/t/t
321 cloning subrepo t from $TESTTMP/t/t
315 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
322 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
316 $ cd tc
323 $ cd tc
317 $ hg debugsub
324 $ hg debugsub
318 path s
325 path s
319 source s
326 source s
320 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
327 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
321 path t
328 path t
322 source t
329 source t
323 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
330 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
324
331
325 push
332 push
326
333
327 $ echo bah > t/t
334 $ echo bah > t/t
328 $ hg ci -m11
335 $ hg ci -m11
329 committing subrepository t
336 committing subrepository t
330 $ hg push
337 $ hg push
331 pushing to $TESTTMP/t (glob)
338 pushing to $TESTTMP/t (glob)
332 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
339 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
333 no changes made to subrepo s since last push to $TESTTMP/t/s
340 no changes made to subrepo s since last push to $TESTTMP/t/s
334 pushing subrepo t to $TESTTMP/t/t
341 pushing subrepo t to $TESTTMP/t/t
335 searching for changes
342 searching for changes
336 adding changesets
343 adding changesets
337 adding manifests
344 adding manifests
338 adding file changes
345 adding file changes
339 added 1 changesets with 1 changes to 1 files
346 added 1 changesets with 1 changes to 1 files
340 searching for changes
347 searching for changes
341 adding changesets
348 adding changesets
342 adding manifests
349 adding manifests
343 adding file changes
350 adding file changes
344 added 1 changesets with 1 changes to 1 files
351 added 1 changesets with 1 changes to 1 files
345
352
346 push -f
353 push -f
347
354
348 $ echo bah > s/a
355 $ echo bah > s/a
349 $ hg ci -m12
356 $ hg ci -m12
350 committing subrepository s
357 committing subrepository s
351 $ hg push
358 $ hg push
352 pushing to $TESTTMP/t (glob)
359 pushing to $TESTTMP/t (glob)
353 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
360 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
354 pushing subrepo s to $TESTTMP/t/s
361 pushing subrepo s to $TESTTMP/t/s
355 searching for changes
362 searching for changes
356 abort: push creates new remote head 12a213df6fa9! (in subrepo s)
363 abort: push creates new remote head 12a213df6fa9! (in subrepo s)
357 (merge or see "hg help push" for details about pushing new heads)
364 (merge or see "hg help push" for details about pushing new heads)
358 [255]
365 [255]
359 $ hg push -f
366 $ hg push -f
360 pushing to $TESTTMP/t (glob)
367 pushing to $TESTTMP/t (glob)
361 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
368 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
362 searching for changes
369 searching for changes
363 no changes found
370 no changes found
364 pushing subrepo s to $TESTTMP/t/s
371 pushing subrepo s to $TESTTMP/t/s
365 searching for changes
372 searching for changes
366 adding changesets
373 adding changesets
367 adding manifests
374 adding manifests
368 adding file changes
375 adding file changes
369 added 1 changesets with 1 changes to 1 files (+1 heads)
376 added 1 changesets with 1 changes to 1 files (+1 heads)
370 pushing subrepo t to $TESTTMP/t/t
377 pushing subrepo t to $TESTTMP/t/t
371 searching for changes
378 searching for changes
372 no changes found
379 no changes found
373 searching for changes
380 searching for changes
374 adding changesets
381 adding changesets
375 adding manifests
382 adding manifests
376 adding file changes
383 adding file changes
377 added 1 changesets with 1 changes to 1 files
384 added 1 changesets with 1 changes to 1 files
378
385
379 check that unmodified subrepos are not pushed
386 check that unmodified subrepos are not pushed
380
387
381 $ hg clone . ../tcc
388 $ hg clone . ../tcc
382 updating to branch default
389 updating to branch default
383 cloning subrepo s from $TESTTMP/tc/s
390 cloning subrepo s from $TESTTMP/tc/s
384 cloning subrepo s/ss from $TESTTMP/tc/s/ss (glob)
391 cloning subrepo s/ss from $TESTTMP/tc/s/ss (glob)
385 cloning subrepo t from $TESTTMP/tc/t
392 cloning subrepo t from $TESTTMP/tc/t
386 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
393 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
387
394
388 the subrepos on the new clone have nothing to push to its source
395 the subrepos on the new clone have nothing to push to its source
389
396
390 $ hg push -R ../tcc .
397 $ hg push -R ../tcc .
391 pushing to .
398 pushing to .
392 no changes made to subrepo s/ss since last push to s/ss (glob)
399 no changes made to subrepo s/ss since last push to s/ss (glob)
393 no changes made to subrepo s since last push to s
400 no changes made to subrepo s since last push to s
394 no changes made to subrepo t since last push to t
401 no changes made to subrepo t since last push to t
395 searching for changes
402 searching for changes
396 no changes found
403 no changes found
397 [1]
404 [1]
398
405
399 the subrepos on the source do not have a clean store versus the clone target
406 the subrepos on the source do not have a clean store versus the clone target
400 because they were never explicitly pushed to the source
407 because they were never explicitly pushed to the source
401
408
402 $ hg push ../tcc
409 $ hg push ../tcc
403 pushing to ../tcc
410 pushing to ../tcc
404 pushing subrepo s/ss to ../tcc/s/ss (glob)
411 pushing subrepo s/ss to ../tcc/s/ss (glob)
405 searching for changes
412 searching for changes
406 no changes found
413 no changes found
407 pushing subrepo s to ../tcc/s
414 pushing subrepo s to ../tcc/s
408 searching for changes
415 searching for changes
409 no changes found
416 no changes found
410 pushing subrepo t to ../tcc/t
417 pushing subrepo t to ../tcc/t
411 searching for changes
418 searching for changes
412 no changes found
419 no changes found
413 searching for changes
420 searching for changes
414 no changes found
421 no changes found
415 [1]
422 [1]
416
423
417 after push their stores become clean
424 after push their stores become clean
418
425
419 $ hg push ../tcc
426 $ hg push ../tcc
420 pushing to ../tcc
427 pushing to ../tcc
421 no changes made to subrepo s/ss since last push to ../tcc/s/ss (glob)
428 no changes made to subrepo s/ss since last push to ../tcc/s/ss (glob)
422 no changes made to subrepo s since last push to ../tcc/s
429 no changes made to subrepo s since last push to ../tcc/s
423 no changes made to subrepo t since last push to ../tcc/t
430 no changes made to subrepo t since last push to ../tcc/t
424 searching for changes
431 searching for changes
425 no changes found
432 no changes found
426 [1]
433 [1]
427
434
428 updating a subrepo to a different revision or changing
435 updating a subrepo to a different revision or changing
429 its working directory does not make its store dirty
436 its working directory does not make its store dirty
430
437
431 $ hg -R s update '.^'
438 $ hg -R s update '.^'
432 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
439 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
433 $ hg push
440 $ hg push
434 pushing to $TESTTMP/t (glob)
441 pushing to $TESTTMP/t (glob)
435 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
442 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
436 no changes made to subrepo s since last push to $TESTTMP/t/s
443 no changes made to subrepo s since last push to $TESTTMP/t/s
437 no changes made to subrepo t since last push to $TESTTMP/t/t
444 no changes made to subrepo t since last push to $TESTTMP/t/t
438 searching for changes
445 searching for changes
439 no changes found
446 no changes found
440 [1]
447 [1]
441 $ echo foo >> s/a
448 $ echo foo >> s/a
442 $ hg push
449 $ hg push
443 pushing to $TESTTMP/t (glob)
450 pushing to $TESTTMP/t (glob)
444 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
451 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
445 no changes made to subrepo s since last push to $TESTTMP/t/s
452 no changes made to subrepo s since last push to $TESTTMP/t/s
446 no changes made to subrepo t since last push to $TESTTMP/t/t
453 no changes made to subrepo t since last push to $TESTTMP/t/t
447 searching for changes
454 searching for changes
448 no changes found
455 no changes found
449 [1]
456 [1]
450 $ hg -R s update -C tip
457 $ hg -R s update -C tip
451 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
458 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
452
459
453 committing into a subrepo makes its store (but not its parent's store) dirty
460 committing into a subrepo makes its store (but not its parent's store) dirty
454
461
455 $ echo foo >> s/ss/a
462 $ echo foo >> s/ss/a
456 $ hg -R s/ss commit -m 'test dirty store detection'
463 $ hg -R s/ss commit -m 'test dirty store detection'
457 $ hg push
464 $ hg push
458 pushing to $TESTTMP/t (glob)
465 pushing to $TESTTMP/t (glob)
459 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
466 pushing subrepo s/ss to $TESTTMP/t/s/ss (glob)
460 searching for changes
467 searching for changes
461 adding changesets
468 adding changesets
462 adding manifests
469 adding manifests
463 adding file changes
470 adding file changes
464 added 1 changesets with 1 changes to 1 files
471 added 1 changesets with 1 changes to 1 files
465 no changes made to subrepo s since last push to $TESTTMP/t/s
472 no changes made to subrepo s since last push to $TESTTMP/t/s
466 no changes made to subrepo t since last push to $TESTTMP/t/t
473 no changes made to subrepo t since last push to $TESTTMP/t/t
467 searching for changes
474 searching for changes
468 no changes found
475 no changes found
469 [1]
476 [1]
470
477
471 a subrepo store may be clean versus one repo but not versus another
478 a subrepo store may be clean versus one repo but not versus another
472
479
473 $ hg push
480 $ hg push
474 pushing to $TESTTMP/t (glob)
481 pushing to $TESTTMP/t (glob)
475 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
482 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss (glob)
476 no changes made to subrepo s since last push to $TESTTMP/t/s
483 no changes made to subrepo s since last push to $TESTTMP/t/s
477 no changes made to subrepo t since last push to $TESTTMP/t/t
484 no changes made to subrepo t since last push to $TESTTMP/t/t
478 searching for changes
485 searching for changes
479 no changes found
486 no changes found
480 [1]
487 [1]
481 $ hg push ../tcc
488 $ hg push ../tcc
482 pushing to ../tcc
489 pushing to ../tcc
483 pushing subrepo s/ss to ../tcc/s/ss (glob)
490 pushing subrepo s/ss to ../tcc/s/ss (glob)
484 searching for changes
491 searching for changes
485 adding changesets
492 adding changesets
486 adding manifests
493 adding manifests
487 adding file changes
494 adding file changes
488 added 1 changesets with 1 changes to 1 files
495 added 1 changesets with 1 changes to 1 files
489 no changes made to subrepo s since last push to ../tcc/s
496 no changes made to subrepo s since last push to ../tcc/s
490 no changes made to subrepo t since last push to ../tcc/t
497 no changes made to subrepo t since last push to ../tcc/t
491 searching for changes
498 searching for changes
492 no changes found
499 no changes found
493 [1]
500 [1]
494
501
495 update
502 update
496
503
497 $ cd ../t
504 $ cd ../t
498 $ hg up -C # discard our earlier merge
505 $ hg up -C # discard our earlier merge
499 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
506 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
500 $ echo blah > t/t
507 $ echo blah > t/t
501 $ hg ci -m13
508 $ hg ci -m13
502 committing subrepository t
509 committing subrepository t
503
510
504 backout calls revert internally with minimal opts, which should not raise
511 backout calls revert internally with minimal opts, which should not raise
505 KeyError
512 KeyError
506
513
507 $ hg backout ".^"
514 $ hg backout ".^"
508 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
515 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
509 changeset c373c8102e68 backed out, don't forget to commit.
516 changeset c373c8102e68 backed out, don't forget to commit.
510
517
511 $ hg up -C # discard changes
518 $ hg up -C # discard changes
512 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
519 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
513
520
514 pull
521 pull
515
522
516 $ cd ../tc
523 $ cd ../tc
517 $ hg pull
524 $ hg pull
518 pulling from $TESTTMP/t (glob)
525 pulling from $TESTTMP/t (glob)
519 searching for changes
526 searching for changes
520 adding changesets
527 adding changesets
521 adding manifests
528 adding manifests
522 adding file changes
529 adding file changes
523 added 1 changesets with 1 changes to 1 files
530 added 1 changesets with 1 changes to 1 files
524 (run 'hg update' to get a working copy)
531 (run 'hg update' to get a working copy)
525
532
526 should pull t
533 should pull t
527
534
528 $ hg up
535 $ hg up
529 pulling subrepo t from $TESTTMP/t/t
536 pulling subrepo t from $TESTTMP/t/t
530 searching for changes
537 searching for changes
531 adding changesets
538 adding changesets
532 adding manifests
539 adding manifests
533 adding file changes
540 adding file changes
534 added 1 changesets with 1 changes to 1 files
541 added 1 changesets with 1 changes to 1 files
535 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
542 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
536 $ cat t/t
543 $ cat t/t
537 blah
544 blah
538
545
539 bogus subrepo path aborts
546 bogus subrepo path aborts
540
547
541 $ echo 'bogus=[boguspath' >> .hgsub
548 $ echo 'bogus=[boguspath' >> .hgsub
542 $ hg ci -m 'bogus subrepo path'
549 $ hg ci -m 'bogus subrepo path'
543 abort: missing ] in subrepo source
550 abort: missing ] in subrepo source
544 [255]
551 [255]
545
552
546 Issue1986: merge aborts when trying to merge a subrepo that
553 Issue1986: merge aborts when trying to merge a subrepo that
547 shouldn't need merging
554 shouldn't need merging
548
555
549 # subrepo layout
556 # subrepo layout
550 #
557 #
551 # o 5 br
558 # o 5 br
552 # /|
559 # /|
553 # o | 4 default
560 # o | 4 default
554 # | |
561 # | |
555 # | o 3 br
562 # | o 3 br
556 # |/|
563 # |/|
557 # o | 2 default
564 # o | 2 default
558 # | |
565 # | |
559 # | o 1 br
566 # | o 1 br
560 # |/
567 # |/
561 # o 0 default
568 # o 0 default
562
569
563 $ cd ..
570 $ cd ..
564 $ rm -rf sub
571 $ rm -rf sub
565 $ hg init main
572 $ hg init main
566 $ cd main
573 $ cd main
567 $ hg init s
574 $ hg init s
568 $ cd s
575 $ cd s
569 $ echo a > a
576 $ echo a > a
570 $ hg ci -Am1
577 $ hg ci -Am1
571 adding a
578 adding a
572 $ hg branch br
579 $ hg branch br
573 marked working directory as branch br
580 marked working directory as branch br
574 (branches are permanent and global, did you want a bookmark?)
581 (branches are permanent and global, did you want a bookmark?)
575 $ echo a >> a
582 $ echo a >> a
576 $ hg ci -m1
583 $ hg ci -m1
577 $ hg up default
584 $ hg up default
578 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
585 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
579 $ echo b > b
586 $ echo b > b
580 $ hg ci -Am1
587 $ hg ci -Am1
581 adding b
588 adding b
582 $ hg up br
589 $ hg up br
583 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
590 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
584 $ hg merge tip
591 $ hg merge tip
585 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
592 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
586 (branch merge, don't forget to commit)
593 (branch merge, don't forget to commit)
587 $ hg ci -m1
594 $ hg ci -m1
588 $ hg up 2
595 $ hg up 2
589 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
596 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
590 $ echo c > c
597 $ echo c > c
591 $ hg ci -Am1
598 $ hg ci -Am1
592 adding c
599 adding c
593 $ hg up 3
600 $ hg up 3
594 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
601 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
595 $ hg merge 4
602 $ hg merge 4
596 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
603 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
597 (branch merge, don't forget to commit)
604 (branch merge, don't forget to commit)
598 $ hg ci -m1
605 $ hg ci -m1
599
606
600 # main repo layout:
607 # main repo layout:
601 #
608 #
602 # * <-- try to merge default into br again
609 # * <-- try to merge default into br again
603 # .`|
610 # .`|
604 # . o 5 br --> substate = 5
611 # . o 5 br --> substate = 5
605 # . |
612 # . |
606 # o | 4 default --> substate = 4
613 # o | 4 default --> substate = 4
607 # | |
614 # | |
608 # | o 3 br --> substate = 2
615 # | o 3 br --> substate = 2
609 # |/|
616 # |/|
610 # o | 2 default --> substate = 2
617 # o | 2 default --> substate = 2
611 # | |
618 # | |
612 # | o 1 br --> substate = 3
619 # | o 1 br --> substate = 3
613 # |/
620 # |/
614 # o 0 default --> substate = 2
621 # o 0 default --> substate = 2
615
622
616 $ cd ..
623 $ cd ..
617 $ echo 's = s' > .hgsub
624 $ echo 's = s' > .hgsub
618 $ hg -R s up 2
625 $ hg -R s up 2
619 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
626 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
620 $ hg ci -Am1
627 $ hg ci -Am1
621 adding .hgsub
628 adding .hgsub
622 $ hg branch br
629 $ hg branch br
623 marked working directory as branch br
630 marked working directory as branch br
624 (branches are permanent and global, did you want a bookmark?)
631 (branches are permanent and global, did you want a bookmark?)
625 $ echo b > b
632 $ echo b > b
626 $ hg -R s up 3
633 $ hg -R s up 3
627 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
634 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
628 $ hg ci -Am1
635 $ hg ci -Am1
629 adding b
636 adding b
630 $ hg up default
637 $ hg up default
631 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
638 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
632 $ echo c > c
639 $ echo c > c
633 $ hg ci -Am1
640 $ hg ci -Am1
634 adding c
641 adding c
635 $ hg up 1
642 $ hg up 1
636 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
643 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
637 $ hg merge 2
644 $ hg merge 2
638 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
645 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
639 (branch merge, don't forget to commit)
646 (branch merge, don't forget to commit)
640 $ hg ci -m1
647 $ hg ci -m1
641 $ hg up 2
648 $ hg up 2
642 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
649 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
643 $ hg -R s up 4
650 $ hg -R s up 4
644 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
651 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
645 $ echo d > d
652 $ echo d > d
646 $ hg ci -Am1
653 $ hg ci -Am1
647 adding d
654 adding d
648 $ hg up 3
655 $ hg up 3
649 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
656 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
650 $ hg -R s up 5
657 $ hg -R s up 5
651 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
658 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
652 $ echo e > e
659 $ echo e > e
653 $ hg ci -Am1
660 $ hg ci -Am1
654 adding e
661 adding e
655
662
656 $ hg up 5
663 $ hg up 5
657 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
664 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
658 $ hg merge 4 # try to merge default into br again
665 $ hg merge 4 # try to merge default into br again
659 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
666 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
660 (M)erge, keep (l)ocal or keep (r)emote? m
667 (M)erge, keep (l)ocal or keep (r)emote? m
661 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
668 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
662 (branch merge, don't forget to commit)
669 (branch merge, don't forget to commit)
663 $ cd ..
670 $ cd ..
664
671
665 test subrepo delete from .hgsubstate
672 test subrepo delete from .hgsubstate
666
673
667 $ hg init testdelete
674 $ hg init testdelete
668 $ mkdir testdelete/nested testdelete/nested2
675 $ mkdir testdelete/nested testdelete/nested2
669 $ hg init testdelete/nested
676 $ hg init testdelete/nested
670 $ hg init testdelete/nested2
677 $ hg init testdelete/nested2
671 $ echo test > testdelete/nested/foo
678 $ echo test > testdelete/nested/foo
672 $ echo test > testdelete/nested2/foo
679 $ echo test > testdelete/nested2/foo
673 $ hg -R testdelete/nested add
680 $ hg -R testdelete/nested add
674 adding testdelete/nested/foo (glob)
681 adding testdelete/nested/foo (glob)
675 $ hg -R testdelete/nested2 add
682 $ hg -R testdelete/nested2 add
676 adding testdelete/nested2/foo (glob)
683 adding testdelete/nested2/foo (glob)
677 $ hg -R testdelete/nested ci -m test
684 $ hg -R testdelete/nested ci -m test
678 $ hg -R testdelete/nested2 ci -m test
685 $ hg -R testdelete/nested2 ci -m test
679 $ echo nested = nested > testdelete/.hgsub
686 $ echo nested = nested > testdelete/.hgsub
680 $ echo nested2 = nested2 >> testdelete/.hgsub
687 $ echo nested2 = nested2 >> testdelete/.hgsub
681 $ hg -R testdelete add
688 $ hg -R testdelete add
682 adding testdelete/.hgsub (glob)
689 adding testdelete/.hgsub (glob)
683 $ hg -R testdelete ci -m "nested 1 & 2 added"
690 $ hg -R testdelete ci -m "nested 1 & 2 added"
684 $ echo nested = nested > testdelete/.hgsub
691 $ echo nested = nested > testdelete/.hgsub
685 $ hg -R testdelete ci -m "nested 2 deleted"
692 $ hg -R testdelete ci -m "nested 2 deleted"
686 $ cat testdelete/.hgsubstate
693 $ cat testdelete/.hgsubstate
687 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
694 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
688 $ hg -R testdelete remove testdelete/.hgsub
695 $ hg -R testdelete remove testdelete/.hgsub
689 $ hg -R testdelete ci -m ".hgsub deleted"
696 $ hg -R testdelete ci -m ".hgsub deleted"
690 $ cat testdelete/.hgsubstate
697 $ cat testdelete/.hgsubstate
691 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
698 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
692
699
693 test repository cloning
700 test repository cloning
694
701
695 $ mkdir mercurial mercurial2
702 $ mkdir mercurial mercurial2
696 $ hg init nested_absolute
703 $ hg init nested_absolute
697 $ echo test > nested_absolute/foo
704 $ echo test > nested_absolute/foo
698 $ hg -R nested_absolute add
705 $ hg -R nested_absolute add
699 adding nested_absolute/foo (glob)
706 adding nested_absolute/foo (glob)
700 $ hg -R nested_absolute ci -mtest
707 $ hg -R nested_absolute ci -mtest
701 $ cd mercurial
708 $ cd mercurial
702 $ hg init nested_relative
709 $ hg init nested_relative
703 $ echo test2 > nested_relative/foo2
710 $ echo test2 > nested_relative/foo2
704 $ hg -R nested_relative add
711 $ hg -R nested_relative add
705 adding nested_relative/foo2 (glob)
712 adding nested_relative/foo2 (glob)
706 $ hg -R nested_relative ci -mtest2
713 $ hg -R nested_relative ci -mtest2
707 $ hg init main
714 $ hg init main
708 $ echo "nested_relative = ../nested_relative" > main/.hgsub
715 $ echo "nested_relative = ../nested_relative" > main/.hgsub
709 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
716 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
710 $ hg -R main add
717 $ hg -R main add
711 adding main/.hgsub (glob)
718 adding main/.hgsub (glob)
712 $ hg -R main ci -m "add subrepos"
719 $ hg -R main ci -m "add subrepos"
713 $ cd ..
720 $ cd ..
714 $ hg clone mercurial/main mercurial2/main
721 $ hg clone mercurial/main mercurial2/main
715 updating to branch default
722 updating to branch default
716 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
723 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
717 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
724 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
718 > mercurial2/main/nested_relative/.hg/hgrc
725 > mercurial2/main/nested_relative/.hg/hgrc
719 [paths]
726 [paths]
720 default = $TESTTMP/mercurial/nested_absolute
727 default = $TESTTMP/mercurial/nested_absolute
721 [paths]
728 [paths]
722 default = $TESTTMP/mercurial/nested_relative
729 default = $TESTTMP/mercurial/nested_relative
723 $ rm -rf mercurial mercurial2
730 $ rm -rf mercurial mercurial2
724
731
725 Issue1977: multirepo push should fail if subrepo push fails
732 Issue1977: multirepo push should fail if subrepo push fails
726
733
727 $ hg init repo
734 $ hg init repo
728 $ hg init repo/s
735 $ hg init repo/s
729 $ echo a > repo/s/a
736 $ echo a > repo/s/a
730 $ hg -R repo/s ci -Am0
737 $ hg -R repo/s ci -Am0
731 adding a
738 adding a
732 $ echo s = s > repo/.hgsub
739 $ echo s = s > repo/.hgsub
733 $ hg -R repo ci -Am1
740 $ hg -R repo ci -Am1
734 adding .hgsub
741 adding .hgsub
735 $ hg clone repo repo2
742 $ hg clone repo repo2
736 updating to branch default
743 updating to branch default
737 cloning subrepo s from $TESTTMP/repo/s
744 cloning subrepo s from $TESTTMP/repo/s
738 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
745 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
739 $ hg -q -R repo2 pull -u
746 $ hg -q -R repo2 pull -u
740 $ echo 1 > repo2/s/a
747 $ echo 1 > repo2/s/a
741 $ hg -R repo2/s ci -m2
748 $ hg -R repo2/s ci -m2
742 $ hg -q -R repo2/s push
749 $ hg -q -R repo2/s push
743 $ hg -R repo2/s up -C 0
750 $ hg -R repo2/s up -C 0
744 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
751 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
745 $ echo 2 > repo2/s/b
752 $ echo 2 > repo2/s/b
746 $ hg -R repo2/s ci -m3 -A
753 $ hg -R repo2/s ci -m3 -A
747 adding b
754 adding b
748 created new head
755 created new head
749 $ hg -R repo2 ci -m3
756 $ hg -R repo2 ci -m3
750 $ hg -q -R repo2 push
757 $ hg -q -R repo2 push
751 abort: push creates new remote head cc505f09a8b2! (in subrepo s)
758 abort: push creates new remote head cc505f09a8b2! (in subrepo s)
752 (merge or see "hg help push" for details about pushing new heads)
759 (merge or see "hg help push" for details about pushing new heads)
753 [255]
760 [255]
754 $ hg -R repo update
761 $ hg -R repo update
755 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
762 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
756
763
757 test if untracked file is not overwritten
764 test if untracked file is not overwritten
758
765
759 $ echo issue3276_ok > repo/s/b
766 $ echo issue3276_ok > repo/s/b
760 $ hg -R repo2 push -f -q
767 $ hg -R repo2 push -f -q
761 $ touch -t 200001010000 repo/.hgsubstate
768 $ touch -t 200001010000 repo/.hgsubstate
762 $ hg -R repo status --config debug.dirstate.delaywrite=2 repo/.hgsubstate
769 $ hg -R repo status --config debug.dirstate.delaywrite=2 repo/.hgsubstate
763 $ hg -R repo update
770 $ hg -R repo update
764 b: untracked file differs
771 b: untracked file differs
765 abort: untracked files in working directory differ from files in requested revision (in subrepo s)
772 abort: untracked files in working directory differ from files in requested revision (in subrepo s)
766 [255]
773 [255]
767
774
768 $ cat repo/s/b
775 $ cat repo/s/b
769 issue3276_ok
776 issue3276_ok
770 $ rm repo/s/b
777 $ rm repo/s/b
771 $ touch -t 200001010000 repo/.hgsubstate
778 $ touch -t 200001010000 repo/.hgsubstate
772 $ hg -R repo revert --all
779 $ hg -R repo revert --all
773 reverting repo/.hgsubstate (glob)
780 reverting repo/.hgsubstate (glob)
774 reverting subrepo s
781 reverting subrepo s
775 $ hg -R repo update
782 $ hg -R repo update
776 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
783 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
777 $ cat repo/s/b
784 $ cat repo/s/b
778 2
785 2
779 $ rm -rf repo2 repo
786 $ rm -rf repo2 repo
780
787
781
788
782 Issue1852 subrepos with relative paths always push/pull relative to default
789 Issue1852 subrepos with relative paths always push/pull relative to default
783
790
784 Prepare a repo with subrepo
791 Prepare a repo with subrepo
785
792
786 $ hg init issue1852a
793 $ hg init issue1852a
787 $ cd issue1852a
794 $ cd issue1852a
788 $ hg init sub/repo
795 $ hg init sub/repo
789 $ echo test > sub/repo/foo
796 $ echo test > sub/repo/foo
790 $ hg -R sub/repo add sub/repo/foo
797 $ hg -R sub/repo add sub/repo/foo
791 $ echo sub/repo = sub/repo > .hgsub
798 $ echo sub/repo = sub/repo > .hgsub
792 $ hg add .hgsub
799 $ hg add .hgsub
793 $ hg ci -mtest
800 $ hg ci -mtest
794 committing subrepository sub/repo (glob)
801 committing subrepository sub/repo (glob)
795 $ echo test >> sub/repo/foo
802 $ echo test >> sub/repo/foo
796 $ hg ci -mtest
803 $ hg ci -mtest
797 committing subrepository sub/repo (glob)
804 committing subrepository sub/repo (glob)
798 $ hg cat sub/repo/foo
805 $ hg cat sub/repo/foo
799 test
806 test
800 test
807 test
801 $ mkdir -p tmp/sub/repo
808 $ mkdir -p tmp/sub/repo
802 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
809 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
803 $ cat tmp/sub/repo/foo_p
810 $ cat tmp/sub/repo/foo_p
804 test
811 test
805 $ mv sub/repo sub_
812 $ mv sub/repo sub_
806 $ hg cat sub/repo/baz
813 $ hg cat sub/repo/baz
807 skipping missing subrepository: sub/repo
814 skipping missing subrepository: sub/repo
808 [1]
815 [1]
809 $ rm -rf sub/repo
816 $ rm -rf sub/repo
810 $ mv sub_ sub/repo
817 $ mv sub_ sub/repo
811 $ cd ..
818 $ cd ..
812
819
813 Create repo without default path, pull top repo, and see what happens on update
820 Create repo without default path, pull top repo, and see what happens on update
814
821
815 $ hg init issue1852b
822 $ hg init issue1852b
816 $ hg -R issue1852b pull issue1852a
823 $ hg -R issue1852b pull issue1852a
817 pulling from issue1852a
824 pulling from issue1852a
818 requesting all changes
825 requesting all changes
819 adding changesets
826 adding changesets
820 adding manifests
827 adding manifests
821 adding file changes
828 adding file changes
822 added 2 changesets with 3 changes to 2 files
829 added 2 changesets with 3 changes to 2 files
823 (run 'hg update' to get a working copy)
830 (run 'hg update' to get a working copy)
824 $ hg -R issue1852b update
831 $ hg -R issue1852b update
825 abort: default path for subrepository not found (in subrepo sub/repo) (glob)
832 abort: default path for subrepository not found (in subrepo sub/repo) (glob)
826 [255]
833 [255]
827
834
828 Ensure a full traceback, not just the SubrepoAbort part
835 Ensure a full traceback, not just the SubrepoAbort part
829
836
830 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise util\.Abort'
837 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise util\.Abort'
831 raise util.Abort(_("default path for subrepository not found"))
838 raise util.Abort(_("default path for subrepository not found"))
832
839
833 Pull -u now doesn't help
840 Pull -u now doesn't help
834
841
835 $ hg -R issue1852b pull -u issue1852a
842 $ hg -R issue1852b pull -u issue1852a
836 pulling from issue1852a
843 pulling from issue1852a
837 searching for changes
844 searching for changes
838 no changes found
845 no changes found
839
846
840 Try the same, but with pull -u
847 Try the same, but with pull -u
841
848
842 $ hg init issue1852c
849 $ hg init issue1852c
843 $ hg -R issue1852c pull -r0 -u issue1852a
850 $ hg -R issue1852c pull -r0 -u issue1852a
844 pulling from issue1852a
851 pulling from issue1852a
845 adding changesets
852 adding changesets
846 adding manifests
853 adding manifests
847 adding file changes
854 adding file changes
848 added 1 changesets with 2 changes to 2 files
855 added 1 changesets with 2 changes to 2 files
849 cloning subrepo sub/repo from issue1852a/sub/repo (glob)
856 cloning subrepo sub/repo from issue1852a/sub/repo (glob)
850 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
857 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
851
858
852 Try to push from the other side
859 Try to push from the other side
853
860
854 $ hg -R issue1852a push `pwd`/issue1852c
861 $ hg -R issue1852a push `pwd`/issue1852c
855 pushing to $TESTTMP/issue1852c (glob)
862 pushing to $TESTTMP/issue1852c (glob)
856 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo (glob)
863 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo (glob)
857 searching for changes
864 searching for changes
858 no changes found
865 no changes found
859 searching for changes
866 searching for changes
860 adding changesets
867 adding changesets
861 adding manifests
868 adding manifests
862 adding file changes
869 adding file changes
863 added 1 changesets with 1 changes to 1 files
870 added 1 changesets with 1 changes to 1 files
864
871
865 Incoming and outgoing should not use the default path:
872 Incoming and outgoing should not use the default path:
866
873
867 $ hg clone -q issue1852a issue1852d
874 $ hg clone -q issue1852a issue1852d
868 $ hg -R issue1852d outgoing --subrepos issue1852c
875 $ hg -R issue1852d outgoing --subrepos issue1852c
869 comparing with issue1852c
876 comparing with issue1852c
870 searching for changes
877 searching for changes
871 no changes found
878 no changes found
872 comparing with issue1852c/sub/repo
879 comparing with issue1852c/sub/repo
873 searching for changes
880 searching for changes
874 no changes found
881 no changes found
875 [1]
882 [1]
876 $ hg -R issue1852d incoming --subrepos issue1852c
883 $ hg -R issue1852d incoming --subrepos issue1852c
877 comparing with issue1852c
884 comparing with issue1852c
878 searching for changes
885 searching for changes
879 no changes found
886 no changes found
880 comparing with issue1852c/sub/repo
887 comparing with issue1852c/sub/repo
881 searching for changes
888 searching for changes
882 no changes found
889 no changes found
883 [1]
890 [1]
884
891
885 Check status of files when none of them belong to the first
892 Check status of files when none of them belong to the first
886 subrepository:
893 subrepository:
887
894
888 $ hg init subrepo-status
895 $ hg init subrepo-status
889 $ cd subrepo-status
896 $ cd subrepo-status
890 $ hg init subrepo-1
897 $ hg init subrepo-1
891 $ hg init subrepo-2
898 $ hg init subrepo-2
892 $ cd subrepo-2
899 $ cd subrepo-2
893 $ touch file
900 $ touch file
894 $ hg add file
901 $ hg add file
895 $ cd ..
902 $ cd ..
896 $ echo subrepo-1 = subrepo-1 > .hgsub
903 $ echo subrepo-1 = subrepo-1 > .hgsub
897 $ echo subrepo-2 = subrepo-2 >> .hgsub
904 $ echo subrepo-2 = subrepo-2 >> .hgsub
898 $ hg add .hgsub
905 $ hg add .hgsub
899 $ hg ci -m 'Added subrepos'
906 $ hg ci -m 'Added subrepos'
900 committing subrepository subrepo-2
907 committing subrepository subrepo-2
901 $ hg st subrepo-2/file
908 $ hg st subrepo-2/file
902
909
903 Check that share works with subrepo
910 Check that share works with subrepo
904 $ hg --config extensions.share= share . ../shared
911 $ hg --config extensions.share= share . ../shared
905 updating working directory
912 updating working directory
906 cloning subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
913 cloning subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
907 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
914 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
908 $ test -f ../shared/subrepo-1/.hg/sharedpath
915 $ test -f ../shared/subrepo-1/.hg/sharedpath
909 [1]
916 [1]
910 $ hg -R ../shared in
917 $ hg -R ../shared in
911 abort: repository default not found!
918 abort: repository default not found!
912 [255]
919 [255]
913 $ hg -R ../shared/subrepo-2 showconfig paths
920 $ hg -R ../shared/subrepo-2 showconfig paths
914 paths.default=$TESTTMP/subrepo-status/subrepo-2
921 paths.default=$TESTTMP/subrepo-status/subrepo-2
915 $ hg -R ../shared/subrepo-1 sum --remote
922 $ hg -R ../shared/subrepo-1 sum --remote
916 parent: -1:000000000000 tip (empty repository)
923 parent: -1:000000000000 tip (empty repository)
917 branch: default
924 branch: default
918 commit: (clean)
925 commit: (clean)
919 update: (current)
926 update: (current)
920 remote: (synced)
927 remote: (synced)
921
928
922 Check hg update --clean
929 Check hg update --clean
923 $ cd $TESTTMP/t
930 $ cd $TESTTMP/t
924 $ rm -r t/t.orig
931 $ rm -r t/t.orig
925 $ hg status -S --all
932 $ hg status -S --all
926 C .hgsub
933 C .hgsub
927 C .hgsubstate
934 C .hgsubstate
928 C a
935 C a
929 C s/.hgsub
936 C s/.hgsub
930 C s/.hgsubstate
937 C s/.hgsubstate
931 C s/a
938 C s/a
932 C s/ss/a
939 C s/ss/a
933 C t/t
940 C t/t
934 $ echo c1 > s/a
941 $ echo c1 > s/a
935 $ cd s
942 $ cd s
936 $ echo c1 > b
943 $ echo c1 > b
937 $ echo c1 > c
944 $ echo c1 > c
938 $ hg add b
945 $ hg add b
939 $ cd ..
946 $ cd ..
940 $ hg status -S
947 $ hg status -S
941 M s/a
948 M s/a
942 A s/b
949 A s/b
943 ? s/c
950 ? s/c
944 $ hg update -C
951 $ hg update -C
945 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
952 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
946 $ hg status -S
953 $ hg status -S
947 ? s/b
954 ? s/b
948 ? s/c
955 ? s/c
949
956
950 Sticky subrepositories, no changes
957 Sticky subrepositories, no changes
951 $ cd $TESTTMP/t
958 $ cd $TESTTMP/t
952 $ hg id
959 $ hg id
953 925c17564ef8 tip
960 925c17564ef8 tip
954 $ hg -R s id
961 $ hg -R s id
955 12a213df6fa9 tip
962 12a213df6fa9 tip
956 $ hg -R t id
963 $ hg -R t id
957 52c0adc0515a tip
964 52c0adc0515a tip
958 $ hg update 11
965 $ hg update 11
959 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
966 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
960 $ hg id
967 $ hg id
961 365661e5936a
968 365661e5936a
962 $ hg -R s id
969 $ hg -R s id
963 fc627a69481f
970 fc627a69481f
964 $ hg -R t id
971 $ hg -R t id
965 e95bcfa18a35
972 e95bcfa18a35
966
973
967 Sticky subrepositories, file changes
974 Sticky subrepositories, file changes
968 $ touch s/f1
975 $ touch s/f1
969 $ touch t/f1
976 $ touch t/f1
970 $ hg add -S s/f1
977 $ hg add -S s/f1
971 $ hg add -S t/f1
978 $ hg add -S t/f1
972 $ hg id
979 $ hg id
973 365661e5936a+
980 365661e5936a+
974 $ hg -R s id
981 $ hg -R s id
975 fc627a69481f+
982 fc627a69481f+
976 $ hg -R t id
983 $ hg -R t id
977 e95bcfa18a35+
984 e95bcfa18a35+
978 $ hg update tip
985 $ hg update tip
979 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
986 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
980 (M)erge, keep (l)ocal or keep (r)emote? m
987 (M)erge, keep (l)ocal or keep (r)emote? m
981 subrepository sources for s differ
988 subrepository sources for s differ
982 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
989 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
983 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
990 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
984 (M)erge, keep (l)ocal or keep (r)emote? m
991 (M)erge, keep (l)ocal or keep (r)emote? m
985 subrepository sources for t differ
992 subrepository sources for t differ
986 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
993 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
987 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
994 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
988 $ hg id
995 $ hg id
989 925c17564ef8+ tip
996 925c17564ef8+ tip
990 $ hg -R s id
997 $ hg -R s id
991 fc627a69481f+
998 fc627a69481f+
992 $ hg -R t id
999 $ hg -R t id
993 e95bcfa18a35+
1000 e95bcfa18a35+
994 $ hg update --clean tip
1001 $ hg update --clean tip
995 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1002 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
996
1003
997 Sticky subrepository, revision updates
1004 Sticky subrepository, revision updates
998 $ hg id
1005 $ hg id
999 925c17564ef8 tip
1006 925c17564ef8 tip
1000 $ hg -R s id
1007 $ hg -R s id
1001 12a213df6fa9 tip
1008 12a213df6fa9 tip
1002 $ hg -R t id
1009 $ hg -R t id
1003 52c0adc0515a tip
1010 52c0adc0515a tip
1004 $ cd s
1011 $ cd s
1005 $ hg update -r -2
1012 $ hg update -r -2
1006 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1013 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1007 $ cd ../t
1014 $ cd ../t
1008 $ hg update -r 2
1015 $ hg update -r 2
1009 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1016 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1010 $ cd ..
1017 $ cd ..
1011 $ hg update 10
1018 $ hg update 10
1012 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1019 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1013 (M)erge, keep (l)ocal or keep (r)emote? m
1020 (M)erge, keep (l)ocal or keep (r)emote? m
1014 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1021 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1015 (M)erge, keep (l)ocal or keep (r)emote? m
1022 (M)erge, keep (l)ocal or keep (r)emote? m
1016 subrepository sources for t differ (in checked out version)
1023 subrepository sources for t differ (in checked out version)
1017 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1024 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1018 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1025 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1019 $ hg id
1026 $ hg id
1020 e45c8b14af55+
1027 e45c8b14af55+
1021 $ hg -R s id
1028 $ hg -R s id
1022 02dcf1d70411
1029 02dcf1d70411
1023 $ hg -R t id
1030 $ hg -R t id
1024 7af322bc1198
1031 7af322bc1198
1025
1032
1026 Sticky subrepository, file changes and revision updates
1033 Sticky subrepository, file changes and revision updates
1027 $ touch s/f1
1034 $ touch s/f1
1028 $ touch t/f1
1035 $ touch t/f1
1029 $ hg add -S s/f1
1036 $ hg add -S s/f1
1030 $ hg add -S t/f1
1037 $ hg add -S t/f1
1031 $ hg id
1038 $ hg id
1032 e45c8b14af55+
1039 e45c8b14af55+
1033 $ hg -R s id
1040 $ hg -R s id
1034 02dcf1d70411+
1041 02dcf1d70411+
1035 $ hg -R t id
1042 $ hg -R t id
1036 7af322bc1198+
1043 7af322bc1198+
1037 $ hg update tip
1044 $ hg update tip
1038 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1045 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1039 (M)erge, keep (l)ocal or keep (r)emote? m
1046 (M)erge, keep (l)ocal or keep (r)emote? m
1040 subrepository sources for s differ
1047 subrepository sources for s differ
1041 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1048 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1042 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1049 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1043 (M)erge, keep (l)ocal or keep (r)emote? m
1050 (M)erge, keep (l)ocal or keep (r)emote? m
1044 subrepository sources for t differ
1051 subrepository sources for t differ
1045 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1052 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1046 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1053 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1047 $ hg id
1054 $ hg id
1048 925c17564ef8+ tip
1055 925c17564ef8+ tip
1049 $ hg -R s id
1056 $ hg -R s id
1050 02dcf1d70411+
1057 02dcf1d70411+
1051 $ hg -R t id
1058 $ hg -R t id
1052 7af322bc1198+
1059 7af322bc1198+
1053
1060
1054 Sticky repository, update --clean
1061 Sticky repository, update --clean
1055 $ hg update --clean tip
1062 $ hg update --clean tip
1056 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1063 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1057 $ hg id
1064 $ hg id
1058 925c17564ef8 tip
1065 925c17564ef8 tip
1059 $ hg -R s id
1066 $ hg -R s id
1060 12a213df6fa9 tip
1067 12a213df6fa9 tip
1061 $ hg -R t id
1068 $ hg -R t id
1062 52c0adc0515a tip
1069 52c0adc0515a tip
1063
1070
1064 Test subrepo already at intended revision:
1071 Test subrepo already at intended revision:
1065 $ cd s
1072 $ cd s
1066 $ hg update fc627a69481f
1073 $ hg update fc627a69481f
1067 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1074 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1068 $ cd ..
1075 $ cd ..
1069 $ hg update 11
1076 $ hg update 11
1070 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1077 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1071 (M)erge, keep (l)ocal or keep (r)emote? m
1078 (M)erge, keep (l)ocal or keep (r)emote? m
1072 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1079 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1073 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1080 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1074 $ hg id -n
1081 $ hg id -n
1075 11+
1082 11+
1076 $ hg -R s id
1083 $ hg -R s id
1077 fc627a69481f
1084 fc627a69481f
1078 $ hg -R t id
1085 $ hg -R t id
1079 e95bcfa18a35
1086 e95bcfa18a35
1080
1087
1081 Test that removing .hgsubstate doesn't break anything:
1088 Test that removing .hgsubstate doesn't break anything:
1082
1089
1083 $ hg rm -f .hgsubstate
1090 $ hg rm -f .hgsubstate
1084 $ hg ci -mrm
1091 $ hg ci -mrm
1085 nothing changed
1092 nothing changed
1086 [1]
1093 [1]
1087 $ hg log -vr tip
1094 $ hg log -vr tip
1088 changeset: 13:925c17564ef8
1095 changeset: 13:925c17564ef8
1089 tag: tip
1096 tag: tip
1090 user: test
1097 user: test
1091 date: Thu Jan 01 00:00:00 1970 +0000
1098 date: Thu Jan 01 00:00:00 1970 +0000
1092 files: .hgsubstate
1099 files: .hgsubstate
1093 description:
1100 description:
1094 13
1101 13
1095
1102
1096
1103
1097
1104
1098 Test that removing .hgsub removes .hgsubstate:
1105 Test that removing .hgsub removes .hgsubstate:
1099
1106
1100 $ hg rm .hgsub
1107 $ hg rm .hgsub
1101 $ hg ci -mrm2
1108 $ hg ci -mrm2
1102 created new head
1109 created new head
1103 $ hg log -vr tip
1110 $ hg log -vr tip
1104 changeset: 14:2400bccd50af
1111 changeset: 14:2400bccd50af
1105 tag: tip
1112 tag: tip
1106 parent: 11:365661e5936a
1113 parent: 11:365661e5936a
1107 user: test
1114 user: test
1108 date: Thu Jan 01 00:00:00 1970 +0000
1115 date: Thu Jan 01 00:00:00 1970 +0000
1109 files: .hgsub .hgsubstate
1116 files: .hgsub .hgsubstate
1110 description:
1117 description:
1111 rm2
1118 rm2
1112
1119
1113
1120
1114 Test issue3153: diff -S with deleted subrepos
1121 Test issue3153: diff -S with deleted subrepos
1115
1122
1116 $ hg diff --nodates -S -c .
1123 $ hg diff --nodates -S -c .
1117 diff -r 365661e5936a -r 2400bccd50af .hgsub
1124 diff -r 365661e5936a -r 2400bccd50af .hgsub
1118 --- a/.hgsub
1125 --- a/.hgsub
1119 +++ /dev/null
1126 +++ /dev/null
1120 @@ -1,2 +0,0 @@
1127 @@ -1,2 +0,0 @@
1121 -s = s
1128 -s = s
1122 -t = t
1129 -t = t
1123 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1130 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1124 --- a/.hgsubstate
1131 --- a/.hgsubstate
1125 +++ /dev/null
1132 +++ /dev/null
1126 @@ -1,2 +0,0 @@
1133 @@ -1,2 +0,0 @@
1127 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1134 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1128 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1135 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1129
1136
1130 Test behavior of add for explicit path in subrepo:
1137 Test behavior of add for explicit path in subrepo:
1131 $ cd ..
1138 $ cd ..
1132 $ hg init explicit
1139 $ hg init explicit
1133 $ cd explicit
1140 $ cd explicit
1134 $ echo s = s > .hgsub
1141 $ echo s = s > .hgsub
1135 $ hg add .hgsub
1142 $ hg add .hgsub
1136 $ hg init s
1143 $ hg init s
1137 $ hg ci -m0
1144 $ hg ci -m0
1138 Adding with an explicit path in a subrepo adds the file
1145 Adding with an explicit path in a subrepo adds the file
1139 $ echo c1 > f1
1146 $ echo c1 > f1
1140 $ echo c2 > s/f2
1147 $ echo c2 > s/f2
1141 $ hg st -S
1148 $ hg st -S
1142 ? f1
1149 ? f1
1143 ? s/f2
1150 ? s/f2
1144 $ hg add s/f2
1151 $ hg add s/f2
1145 $ hg st -S
1152 $ hg st -S
1146 A s/f2
1153 A s/f2
1147 ? f1
1154 ? f1
1148 $ hg ci -R s -m0
1155 $ hg ci -R s -m0
1149 $ hg ci -Am1
1156 $ hg ci -Am1
1150 adding f1
1157 adding f1
1151 Adding with an explicit path in a subrepo with -S has the same behavior
1158 Adding with an explicit path in a subrepo with -S has the same behavior
1152 $ echo c3 > f3
1159 $ echo c3 > f3
1153 $ echo c4 > s/f4
1160 $ echo c4 > s/f4
1154 $ hg st -S
1161 $ hg st -S
1155 ? f3
1162 ? f3
1156 ? s/f4
1163 ? s/f4
1157 $ hg add -S s/f4
1164 $ hg add -S s/f4
1158 $ hg st -S
1165 $ hg st -S
1159 A s/f4
1166 A s/f4
1160 ? f3
1167 ? f3
1161 $ hg ci -R s -m1
1168 $ hg ci -R s -m1
1162 $ hg ci -Ama2
1169 $ hg ci -Ama2
1163 adding f3
1170 adding f3
1164 Adding without a path or pattern silently ignores subrepos
1171 Adding without a path or pattern silently ignores subrepos
1165 $ echo c5 > f5
1172 $ echo c5 > f5
1166 $ echo c6 > s/f6
1173 $ echo c6 > s/f6
1167 $ echo c7 > s/f7
1174 $ echo c7 > s/f7
1168 $ hg st -S
1175 $ hg st -S
1169 ? f5
1176 ? f5
1170 ? s/f6
1177 ? s/f6
1171 ? s/f7
1178 ? s/f7
1172 $ hg add
1179 $ hg add
1173 adding f5
1180 adding f5
1174 $ hg st -S
1181 $ hg st -S
1175 A f5
1182 A f5
1176 ? s/f6
1183 ? s/f6
1177 ? s/f7
1184 ? s/f7
1178 $ hg ci -R s -Am2
1185 $ hg ci -R s -Am2
1179 adding f6
1186 adding f6
1180 adding f7
1187 adding f7
1181 $ hg ci -m3
1188 $ hg ci -m3
1182 Adding without a path or pattern with -S also adds files in subrepos
1189 Adding without a path or pattern with -S also adds files in subrepos
1183 $ echo c8 > f8
1190 $ echo c8 > f8
1184 $ echo c9 > s/f9
1191 $ echo c9 > s/f9
1185 $ echo c10 > s/f10
1192 $ echo c10 > s/f10
1186 $ hg st -S
1193 $ hg st -S
1187 ? f8
1194 ? f8
1188 ? s/f10
1195 ? s/f10
1189 ? s/f9
1196 ? s/f9
1190 $ hg add -S
1197 $ hg add -S
1191 adding f8
1198 adding f8
1192 adding s/f10 (glob)
1199 adding s/f10 (glob)
1193 adding s/f9 (glob)
1200 adding s/f9 (glob)
1194 $ hg st -S
1201 $ hg st -S
1195 A f8
1202 A f8
1196 A s/f10
1203 A s/f10
1197 A s/f9
1204 A s/f9
1198 $ hg ci -R s -m3
1205 $ hg ci -R s -m3
1199 $ hg ci -m4
1206 $ hg ci -m4
1200 Adding with a pattern silently ignores subrepos
1207 Adding with a pattern silently ignores subrepos
1201 $ echo c11 > fm11
1208 $ echo c11 > fm11
1202 $ echo c12 > fn12
1209 $ echo c12 > fn12
1203 $ echo c13 > s/fm13
1210 $ echo c13 > s/fm13
1204 $ echo c14 > s/fn14
1211 $ echo c14 > s/fn14
1205 $ hg st -S
1212 $ hg st -S
1206 ? fm11
1213 ? fm11
1207 ? fn12
1214 ? fn12
1208 ? s/fm13
1215 ? s/fm13
1209 ? s/fn14
1216 ? s/fn14
1210 $ hg add 'glob:**fm*'
1217 $ hg add 'glob:**fm*'
1211 adding fm11
1218 adding fm11
1212 $ hg st -S
1219 $ hg st -S
1213 A fm11
1220 A fm11
1214 ? fn12
1221 ? fn12
1215 ? s/fm13
1222 ? s/fm13
1216 ? s/fn14
1223 ? s/fn14
1217 $ hg ci -R s -Am4
1224 $ hg ci -R s -Am4
1218 adding fm13
1225 adding fm13
1219 adding fn14
1226 adding fn14
1220 $ hg ci -Am5
1227 $ hg ci -Am5
1221 adding fn12
1228 adding fn12
1222 Adding with a pattern with -S also adds matches in subrepos
1229 Adding with a pattern with -S also adds matches in subrepos
1223 $ echo c15 > fm15
1230 $ echo c15 > fm15
1224 $ echo c16 > fn16
1231 $ echo c16 > fn16
1225 $ echo c17 > s/fm17
1232 $ echo c17 > s/fm17
1226 $ echo c18 > s/fn18
1233 $ echo c18 > s/fn18
1227 $ hg st -S
1234 $ hg st -S
1228 ? fm15
1235 ? fm15
1229 ? fn16
1236 ? fn16
1230 ? s/fm17
1237 ? s/fm17
1231 ? s/fn18
1238 ? s/fn18
1232 $ hg add -S 'glob:**fm*'
1239 $ hg add -S 'glob:**fm*'
1233 adding fm15
1240 adding fm15
1234 adding s/fm17 (glob)
1241 adding s/fm17 (glob)
1235 $ hg st -S
1242 $ hg st -S
1236 A fm15
1243 A fm15
1237 A s/fm17
1244 A s/fm17
1238 ? fn16
1245 ? fn16
1239 ? s/fn18
1246 ? s/fn18
1240 $ hg ci -R s -Am5
1247 $ hg ci -R s -Am5
1241 adding fn18
1248 adding fn18
1242 $ hg ci -Am6
1249 $ hg ci -Am6
1243 adding fn16
1250 adding fn16
1244
1251
1245 Test behavior of forget for explicit path in subrepo:
1252 Test behavior of forget for explicit path in subrepo:
1246 Forgetting an explicit path in a subrepo untracks the file
1253 Forgetting an explicit path in a subrepo untracks the file
1247 $ echo c19 > s/f19
1254 $ echo c19 > s/f19
1248 $ hg add s/f19
1255 $ hg add s/f19
1249 $ hg st -S
1256 $ hg st -S
1250 A s/f19
1257 A s/f19
1251 $ hg forget s/f19
1258 $ hg forget s/f19
1252 $ hg st -S
1259 $ hg st -S
1253 ? s/f19
1260 ? s/f19
1254 $ rm s/f19
1261 $ rm s/f19
1255 $ cd ..
1262 $ cd ..
1256
1263
1257 Courtesy phases synchronisation to publishing server does not block the push
1264 Courtesy phases synchronisation to publishing server does not block the push
1258 (issue3781)
1265 (issue3781)
1259
1266
1260 $ cp -r main issue3781
1267 $ cp -r main issue3781
1261 $ cp -r main issue3781-dest
1268 $ cp -r main issue3781-dest
1262 $ cd issue3781-dest/s
1269 $ cd issue3781-dest/s
1263 $ hg phase tip # show we have draft changeset
1270 $ hg phase tip # show we have draft changeset
1264 5: draft
1271 5: draft
1265 $ chmod a-w .hg/store/phaseroots # prevent phase push
1272 $ chmod a-w .hg/store/phaseroots # prevent phase push
1266 $ cd ../../issue3781
1273 $ cd ../../issue3781
1267 $ cat >> .hg/hgrc << EOF
1274 $ cat >> .hg/hgrc << EOF
1268 > [paths]
1275 > [paths]
1269 > default=../issue3781-dest/
1276 > default=../issue3781-dest/
1270 > EOF
1277 > EOF
1271 $ hg push
1278 $ hg push
1272 pushing to $TESTTMP/issue3781-dest (glob)
1279 pushing to $TESTTMP/issue3781-dest (glob)
1273 pushing subrepo s to $TESTTMP/issue3781-dest/s
1280 pushing subrepo s to $TESTTMP/issue3781-dest/s
1274 searching for changes
1281 searching for changes
1275 no changes found
1282 no changes found
1276 searching for changes
1283 searching for changes
1277 no changes found
1284 no changes found
1278 [1]
1285 [1]
1279 $ cd ..
1286 $ cd ..
1280
1287
1281 Test phase choice for newly created commit with "phases.subrepochecks"
1288 Test phase choice for newly created commit with "phases.subrepochecks"
1282 configuration
1289 configuration
1283
1290
1284 $ cd t
1291 $ cd t
1285 $ hg update -q -r 12
1292 $ hg update -q -r 12
1286
1293
1287 $ cat >> s/ss/.hg/hgrc <<EOF
1294 $ cat >> s/ss/.hg/hgrc <<EOF
1288 > [phases]
1295 > [phases]
1289 > new-commit = secret
1296 > new-commit = secret
1290 > EOF
1297 > EOF
1291 $ cat >> s/.hg/hgrc <<EOF
1298 $ cat >> s/.hg/hgrc <<EOF
1292 > [phases]
1299 > [phases]
1293 > new-commit = draft
1300 > new-commit = draft
1294 > EOF
1301 > EOF
1295 $ echo phasecheck1 >> s/ss/a
1302 $ echo phasecheck1 >> s/ss/a
1296 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1303 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1297 committing subrepository ss
1304 committing subrepository ss
1298 transaction abort!
1305 transaction abort!
1299 rollback completed
1306 rollback completed
1300 abort: can't commit in draft phase conflicting secret from subrepository ss
1307 abort: can't commit in draft phase conflicting secret from subrepository ss
1301 [255]
1308 [255]
1302 $ echo phasecheck2 >> s/ss/a
1309 $ echo phasecheck2 >> s/ss/a
1303 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1310 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1304 committing subrepository ss
1311 committing subrepository ss
1305 $ hg -R s/ss phase tip
1312 $ hg -R s/ss phase tip
1306 3: secret
1313 3: secret
1307 $ hg -R s phase tip
1314 $ hg -R s phase tip
1308 6: draft
1315 6: draft
1309 $ echo phasecheck3 >> s/ss/a
1316 $ echo phasecheck3 >> s/ss/a
1310 $ hg -R s commit -S -m phasecheck3
1317 $ hg -R s commit -S -m phasecheck3
1311 committing subrepository ss
1318 committing subrepository ss
1312 warning: changes are committed in secret phase from subrepository ss
1319 warning: changes are committed in secret phase from subrepository ss
1313 $ hg -R s/ss phase tip
1320 $ hg -R s/ss phase tip
1314 4: secret
1321 4: secret
1315 $ hg -R s phase tip
1322 $ hg -R s phase tip
1316 7: secret
1323 7: secret
1317
1324
1318 $ cat >> t/.hg/hgrc <<EOF
1325 $ cat >> t/.hg/hgrc <<EOF
1319 > [phases]
1326 > [phases]
1320 > new-commit = draft
1327 > new-commit = draft
1321 > EOF
1328 > EOF
1322 $ cat >> .hg/hgrc <<EOF
1329 $ cat >> .hg/hgrc <<EOF
1323 > [phases]
1330 > [phases]
1324 > new-commit = public
1331 > new-commit = public
1325 > EOF
1332 > EOF
1326 $ echo phasecheck4 >> s/ss/a
1333 $ echo phasecheck4 >> s/ss/a
1327 $ echo phasecheck4 >> t/t
1334 $ echo phasecheck4 >> t/t
1328 $ hg commit -S -m phasecheck4
1335 $ hg commit -S -m phasecheck4
1329 committing subrepository s
1336 committing subrepository s
1330 committing subrepository s/ss (glob)
1337 committing subrepository s/ss (glob)
1331 warning: changes are committed in secret phase from subrepository ss
1338 warning: changes are committed in secret phase from subrepository ss
1332 committing subrepository t
1339 committing subrepository t
1333 warning: changes are committed in secret phase from subrepository s
1340 warning: changes are committed in secret phase from subrepository s
1334 created new head
1341 created new head
1335 $ hg -R s/ss phase tip
1342 $ hg -R s/ss phase tip
1336 5: secret
1343 5: secret
1337 $ hg -R s phase tip
1344 $ hg -R s phase tip
1338 8: secret
1345 8: secret
1339 $ hg -R t phase tip
1346 $ hg -R t phase tip
1340 6: draft
1347 6: draft
1341 $ hg phase tip
1348 $ hg phase tip
1342 15: secret
1349 15: secret
1343
1350
1344 $ cd ..
1351 $ cd ..
1345
1352
1346
1353
1347 Test that commit --secret works on both repo and subrepo (issue4182)
1354 Test that commit --secret works on both repo and subrepo (issue4182)
1348
1355
1349 $ cd main
1356 $ cd main
1350 $ echo secret >> b
1357 $ echo secret >> b
1351 $ echo secret >> s/b
1358 $ echo secret >> s/b
1352 $ hg commit --secret --subrepo -m "secret"
1359 $ hg commit --secret --subrepo -m "secret"
1353 committing subrepository s
1360 committing subrepository s
1354 $ hg phase -r .
1361 $ hg phase -r .
1355 6: secret
1362 6: secret
1356 $ cd s
1363 $ cd s
1357 $ hg phase -r .
1364 $ hg phase -r .
1358 6: secret
1365 6: secret
1359 $ cd ../../
1366 $ cd ../../
1360
1367
1361 Test "subrepos" template keyword
1368 Test "subrepos" template keyword
1362
1369
1363 $ cd t
1370 $ cd t
1364 $ hg update -q 15
1371 $ hg update -q 15
1365 $ cat > .hgsub <<EOF
1372 $ cat > .hgsub <<EOF
1366 > s = s
1373 > s = s
1367 > EOF
1374 > EOF
1368 $ hg commit -m "16"
1375 $ hg commit -m "16"
1369 warning: changes are committed in secret phase from subrepository s
1376 warning: changes are committed in secret phase from subrepository s
1370
1377
1371 (addition of ".hgsub" itself)
1378 (addition of ".hgsub" itself)
1372
1379
1373 $ hg diff --nodates -c 1 .hgsubstate
1380 $ hg diff --nodates -c 1 .hgsubstate
1374 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1381 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1375 --- /dev/null
1382 --- /dev/null
1376 +++ b/.hgsubstate
1383 +++ b/.hgsubstate
1377 @@ -0,0 +1,1 @@
1384 @@ -0,0 +1,1 @@
1378 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1385 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1379 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1386 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1380 f7b1eb17ad24 000000000000
1387 f7b1eb17ad24 000000000000
1381 s
1388 s
1382
1389
1383 (modification of existing entry)
1390 (modification of existing entry)
1384
1391
1385 $ hg diff --nodates -c 2 .hgsubstate
1392 $ hg diff --nodates -c 2 .hgsubstate
1386 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1393 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1387 --- a/.hgsubstate
1394 --- a/.hgsubstate
1388 +++ b/.hgsubstate
1395 +++ b/.hgsubstate
1389 @@ -1,1 +1,1 @@
1396 @@ -1,1 +1,1 @@
1390 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1397 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1391 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1398 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1392 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1399 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1393 7cf8cfea66e4 000000000000
1400 7cf8cfea66e4 000000000000
1394 s
1401 s
1395
1402
1396 (addition of entry)
1403 (addition of entry)
1397
1404
1398 $ hg diff --nodates -c 5 .hgsubstate
1405 $ hg diff --nodates -c 5 .hgsubstate
1399 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1406 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1400 --- a/.hgsubstate
1407 --- a/.hgsubstate
1401 +++ b/.hgsubstate
1408 +++ b/.hgsubstate
1402 @@ -1,1 +1,2 @@
1409 @@ -1,1 +1,2 @@
1403 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1410 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1404 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1411 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1405 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1412 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1406 7cf8cfea66e4 000000000000
1413 7cf8cfea66e4 000000000000
1407 t
1414 t
1408
1415
1409 (removal of existing entry)
1416 (removal of existing entry)
1410
1417
1411 $ hg diff --nodates -c 16 .hgsubstate
1418 $ hg diff --nodates -c 16 .hgsubstate
1412 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1419 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1413 --- a/.hgsubstate
1420 --- a/.hgsubstate
1414 +++ b/.hgsubstate
1421 +++ b/.hgsubstate
1415 @@ -1,2 +1,1 @@
1422 @@ -1,2 +1,1 @@
1416 0731af8ca9423976d3743119d0865097c07bdc1b s
1423 0731af8ca9423976d3743119d0865097c07bdc1b s
1417 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1424 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1418 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1425 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1419 8bec38d2bd0b 000000000000
1426 8bec38d2bd0b 000000000000
1420 t
1427 t
1421
1428
1422 (merging)
1429 (merging)
1423
1430
1424 $ hg diff --nodates -c 9 .hgsubstate
1431 $ hg diff --nodates -c 9 .hgsubstate
1425 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1432 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1426 --- a/.hgsubstate
1433 --- a/.hgsubstate
1427 +++ b/.hgsubstate
1434 +++ b/.hgsubstate
1428 @@ -1,1 +1,2 @@
1435 @@ -1,1 +1,2 @@
1429 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1436 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1430 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1437 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1431 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1438 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1432 f6affe3fbfaa 1f14a2e2d3ec
1439 f6affe3fbfaa 1f14a2e2d3ec
1433 t
1440 t
1434
1441
1435 (removal of ".hgsub" itself)
1442 (removal of ".hgsub" itself)
1436
1443
1437 $ hg diff --nodates -c 8 .hgsubstate
1444 $ hg diff --nodates -c 8 .hgsubstate
1438 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1445 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1439 --- a/.hgsubstate
1446 --- a/.hgsubstate
1440 +++ /dev/null
1447 +++ /dev/null
1441 @@ -1,2 +0,0 @@
1448 @@ -1,2 +0,0 @@
1442 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1449 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1443 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1450 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1444 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1451 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1445 f94576341bcf 000000000000
1452 f94576341bcf 000000000000
1446
1453
1447 Test that '[paths]' is configured correctly at subrepo creation
1454 Test that '[paths]' is configured correctly at subrepo creation
1448
1455
1449 $ cd $TESTTMP/tc
1456 $ cd $TESTTMP/tc
1450 $ cat > .hgsub <<EOF
1457 $ cat > .hgsub <<EOF
1451 > # to clear bogus subrepo path 'bogus=[boguspath'
1458 > # to clear bogus subrepo path 'bogus=[boguspath'
1452 > s = s
1459 > s = s
1453 > t = t
1460 > t = t
1454 > EOF
1461 > EOF
1455 $ hg update -q --clean null
1462 $ hg update -q --clean null
1456 $ rm -rf s t
1463 $ rm -rf s t
1457 $ cat >> .hg/hgrc <<EOF
1464 $ cat >> .hg/hgrc <<EOF
1458 > [paths]
1465 > [paths]
1459 > default-push = /foo/bar
1466 > default-push = /foo/bar
1460 > EOF
1467 > EOF
1461 $ hg update -q
1468 $ hg update -q
1462 $ cat s/.hg/hgrc
1469 $ cat s/.hg/hgrc
1463 [paths]
1470 [paths]
1464 default = $TESTTMP/t/s
1471 default = $TESTTMP/t/s
1465 default-push = /foo/bar/s
1472 default-push = /foo/bar/s
1466 $ cat s/ss/.hg/hgrc
1473 $ cat s/ss/.hg/hgrc
1467 [paths]
1474 [paths]
1468 default = $TESTTMP/t/s/ss
1475 default = $TESTTMP/t/s/ss
1469 default-push = /foo/bar/s/ss
1476 default-push = /foo/bar/s/ss
1470 $ cat t/.hg/hgrc
1477 $ cat t/.hg/hgrc
1471 [paths]
1478 [paths]
1472 default = $TESTTMP/t/t
1479 default = $TESTTMP/t/t
1473 default-push = /foo/bar/t
1480 default-push = /foo/bar/t
1474 $ cd ..
1481 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now