##// END OF EJS Templates
revert: add support for reverting subrepos...
Angel Ezquerra -
r16429:71dcce39 default
parent child Browse files
Show More
@@ -1,1518 +1,1525
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 subrepo
13 import subrepo
14
14
15 def parsealiases(cmd):
15 def parsealiases(cmd):
16 return cmd.lstrip("^").split("|")
16 return cmd.lstrip("^").split("|")
17
17
18 def findpossible(cmd, table, strict=False):
18 def findpossible(cmd, table, strict=False):
19 """
19 """
20 Return cmd -> (aliases, command table entry)
20 Return cmd -> (aliases, command table entry)
21 for each matching command.
21 for each matching command.
22 Return debug commands (or their aliases) only if no normal command matches.
22 Return debug commands (or their aliases) only if no normal command matches.
23 """
23 """
24 choice = {}
24 choice = {}
25 debugchoice = {}
25 debugchoice = {}
26
26
27 if cmd in table:
27 if cmd in table:
28 # short-circuit exact matches, "log" alias beats "^log|history"
28 # short-circuit exact matches, "log" alias beats "^log|history"
29 keys = [cmd]
29 keys = [cmd]
30 else:
30 else:
31 keys = table.keys()
31 keys = table.keys()
32
32
33 for e in keys:
33 for e in keys:
34 aliases = parsealiases(e)
34 aliases = parsealiases(e)
35 found = None
35 found = None
36 if cmd in aliases:
36 if cmd in aliases:
37 found = cmd
37 found = cmd
38 elif not strict:
38 elif not strict:
39 for a in aliases:
39 for a in aliases:
40 if a.startswith(cmd):
40 if a.startswith(cmd):
41 found = a
41 found = a
42 break
42 break
43 if found is not None:
43 if found is not None:
44 if aliases[0].startswith("debug") or found.startswith("debug"):
44 if aliases[0].startswith("debug") or found.startswith("debug"):
45 debugchoice[found] = (aliases, table[e])
45 debugchoice[found] = (aliases, table[e])
46 else:
46 else:
47 choice[found] = (aliases, table[e])
47 choice[found] = (aliases, table[e])
48
48
49 if not choice and debugchoice:
49 if not choice and debugchoice:
50 choice = debugchoice
50 choice = debugchoice
51
51
52 return choice
52 return choice
53
53
54 def findcmd(cmd, table, strict=True):
54 def findcmd(cmd, table, strict=True):
55 """Return (aliases, command table entry) for command string."""
55 """Return (aliases, command table entry) for command string."""
56 choice = findpossible(cmd, table, strict)
56 choice = findpossible(cmd, table, strict)
57
57
58 if cmd in choice:
58 if cmd in choice:
59 return choice[cmd]
59 return choice[cmd]
60
60
61 if len(choice) > 1:
61 if len(choice) > 1:
62 clist = choice.keys()
62 clist = choice.keys()
63 clist.sort()
63 clist.sort()
64 raise error.AmbiguousCommand(cmd, clist)
64 raise error.AmbiguousCommand(cmd, clist)
65
65
66 if choice:
66 if choice:
67 return choice.values()[0]
67 return choice.values()[0]
68
68
69 raise error.UnknownCommand(cmd)
69 raise error.UnknownCommand(cmd)
70
70
71 def findrepo(p):
71 def findrepo(p):
72 while not os.path.isdir(os.path.join(p, ".hg")):
72 while not os.path.isdir(os.path.join(p, ".hg")):
73 oldp, p = p, os.path.dirname(p)
73 oldp, p = p, os.path.dirname(p)
74 if p == oldp:
74 if p == oldp:
75 return None
75 return None
76
76
77 return p
77 return p
78
78
79 def bailifchanged(repo):
79 def bailifchanged(repo):
80 if repo.dirstate.p2() != nullid:
80 if repo.dirstate.p2() != nullid:
81 raise util.Abort(_('outstanding uncommitted merge'))
81 raise util.Abort(_('outstanding uncommitted merge'))
82 modified, added, removed, deleted = repo.status()[:4]
82 modified, added, removed, deleted = repo.status()[:4]
83 if modified or added or removed or deleted:
83 if modified or added or removed or deleted:
84 raise util.Abort(_("outstanding uncommitted changes"))
84 raise util.Abort(_("outstanding uncommitted changes"))
85 ctx = repo[None]
85 ctx = repo[None]
86 for s in ctx.substate:
86 for s in ctx.substate:
87 if ctx.sub(s).dirty():
87 if ctx.sub(s).dirty():
88 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
88 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
89
89
90 def logmessage(ui, opts):
90 def logmessage(ui, opts):
91 """ get the log message according to -m and -l option """
91 """ get the log message according to -m and -l option """
92 message = opts.get('message')
92 message = opts.get('message')
93 logfile = opts.get('logfile')
93 logfile = opts.get('logfile')
94
94
95 if message and logfile:
95 if message and logfile:
96 raise util.Abort(_('options --message and --logfile are mutually '
96 raise util.Abort(_('options --message and --logfile are mutually '
97 'exclusive'))
97 'exclusive'))
98 if not message and logfile:
98 if not message and logfile:
99 try:
99 try:
100 if logfile == '-':
100 if logfile == '-':
101 message = ui.fin.read()
101 message = ui.fin.read()
102 else:
102 else:
103 message = '\n'.join(util.readfile(logfile).splitlines())
103 message = '\n'.join(util.readfile(logfile).splitlines())
104 except IOError, inst:
104 except IOError, inst:
105 raise util.Abort(_("can't read commit message '%s': %s") %
105 raise util.Abort(_("can't read commit message '%s': %s") %
106 (logfile, inst.strerror))
106 (logfile, inst.strerror))
107 return message
107 return message
108
108
109 def loglimit(opts):
109 def loglimit(opts):
110 """get the log limit according to option -l/--limit"""
110 """get the log limit according to option -l/--limit"""
111 limit = opts.get('limit')
111 limit = opts.get('limit')
112 if limit:
112 if limit:
113 try:
113 try:
114 limit = int(limit)
114 limit = int(limit)
115 except ValueError:
115 except ValueError:
116 raise util.Abort(_('limit must be a positive integer'))
116 raise util.Abort(_('limit must be a positive integer'))
117 if limit <= 0:
117 if limit <= 0:
118 raise util.Abort(_('limit must be positive'))
118 raise util.Abort(_('limit must be positive'))
119 else:
119 else:
120 limit = None
120 limit = None
121 return limit
121 return limit
122
122
123 def makefilename(repo, pat, node, desc=None,
123 def makefilename(repo, pat, node, desc=None,
124 total=None, seqno=None, revwidth=None, pathname=None):
124 total=None, seqno=None, revwidth=None, pathname=None):
125 node_expander = {
125 node_expander = {
126 'H': lambda: hex(node),
126 'H': lambda: hex(node),
127 'R': lambda: str(repo.changelog.rev(node)),
127 'R': lambda: str(repo.changelog.rev(node)),
128 'h': lambda: short(node),
128 'h': lambda: short(node),
129 'm': lambda: re.sub('[^\w]', '_', str(desc))
129 'm': lambda: re.sub('[^\w]', '_', str(desc))
130 }
130 }
131 expander = {
131 expander = {
132 '%': lambda: '%',
132 '%': lambda: '%',
133 'b': lambda: os.path.basename(repo.root),
133 'b': lambda: os.path.basename(repo.root),
134 }
134 }
135
135
136 try:
136 try:
137 if node:
137 if node:
138 expander.update(node_expander)
138 expander.update(node_expander)
139 if node:
139 if node:
140 expander['r'] = (lambda:
140 expander['r'] = (lambda:
141 str(repo.changelog.rev(node)).zfill(revwidth or 0))
141 str(repo.changelog.rev(node)).zfill(revwidth or 0))
142 if total is not None:
142 if total is not None:
143 expander['N'] = lambda: str(total)
143 expander['N'] = lambda: str(total)
144 if seqno is not None:
144 if seqno is not None:
145 expander['n'] = lambda: str(seqno)
145 expander['n'] = lambda: str(seqno)
146 if total is not None and seqno is not None:
146 if total is not None and seqno is not None:
147 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
147 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
148 if pathname is not None:
148 if pathname is not None:
149 expander['s'] = lambda: os.path.basename(pathname)
149 expander['s'] = lambda: os.path.basename(pathname)
150 expander['d'] = lambda: os.path.dirname(pathname) or '.'
150 expander['d'] = lambda: os.path.dirname(pathname) or '.'
151 expander['p'] = lambda: pathname
151 expander['p'] = lambda: pathname
152
152
153 newname = []
153 newname = []
154 patlen = len(pat)
154 patlen = len(pat)
155 i = 0
155 i = 0
156 while i < patlen:
156 while i < patlen:
157 c = pat[i]
157 c = pat[i]
158 if c == '%':
158 if c == '%':
159 i += 1
159 i += 1
160 c = pat[i]
160 c = pat[i]
161 c = expander[c]()
161 c = expander[c]()
162 newname.append(c)
162 newname.append(c)
163 i += 1
163 i += 1
164 return ''.join(newname)
164 return ''.join(newname)
165 except KeyError, inst:
165 except KeyError, inst:
166 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
166 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
167 inst.args[0])
167 inst.args[0])
168
168
169 def makefileobj(repo, pat, node=None, desc=None, total=None,
169 def makefileobj(repo, pat, node=None, desc=None, total=None,
170 seqno=None, revwidth=None, mode='wb', pathname=None):
170 seqno=None, revwidth=None, mode='wb', pathname=None):
171
171
172 writable = mode not in ('r', 'rb')
172 writable = mode not in ('r', 'rb')
173
173
174 if not pat or pat == '-':
174 if not pat or pat == '-':
175 fp = writable and repo.ui.fout or repo.ui.fin
175 fp = writable and repo.ui.fout or repo.ui.fin
176 if util.safehasattr(fp, 'fileno'):
176 if util.safehasattr(fp, 'fileno'):
177 return os.fdopen(os.dup(fp.fileno()), mode)
177 return os.fdopen(os.dup(fp.fileno()), mode)
178 else:
178 else:
179 # if this fp can't be duped properly, return
179 # if this fp can't be duped properly, return
180 # a dummy object that can be closed
180 # a dummy object that can be closed
181 class wrappedfileobj(object):
181 class wrappedfileobj(object):
182 noop = lambda x: None
182 noop = lambda x: None
183 def __init__(self, f):
183 def __init__(self, f):
184 self.f = f
184 self.f = f
185 def __getattr__(self, attr):
185 def __getattr__(self, attr):
186 if attr == 'close':
186 if attr == 'close':
187 return self.noop
187 return self.noop
188 else:
188 else:
189 return getattr(self.f, attr)
189 return getattr(self.f, attr)
190
190
191 return wrappedfileobj(fp)
191 return wrappedfileobj(fp)
192 if util.safehasattr(pat, 'write') and writable:
192 if util.safehasattr(pat, 'write') and writable:
193 return pat
193 return pat
194 if util.safehasattr(pat, 'read') and 'r' in mode:
194 if util.safehasattr(pat, 'read') and 'r' in mode:
195 return pat
195 return pat
196 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
196 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
197 pathname),
197 pathname),
198 mode)
198 mode)
199
199
200 def openrevlog(repo, cmd, file_, opts):
200 def openrevlog(repo, cmd, file_, opts):
201 """opens the changelog, manifest, a filelog or a given revlog"""
201 """opens the changelog, manifest, a filelog or a given revlog"""
202 cl = opts['changelog']
202 cl = opts['changelog']
203 mf = opts['manifest']
203 mf = opts['manifest']
204 msg = None
204 msg = None
205 if cl and mf:
205 if cl and mf:
206 msg = _('cannot specify --changelog and --manifest at the same time')
206 msg = _('cannot specify --changelog and --manifest at the same time')
207 elif cl or mf:
207 elif cl or mf:
208 if file_:
208 if file_:
209 msg = _('cannot specify filename with --changelog or --manifest')
209 msg = _('cannot specify filename with --changelog or --manifest')
210 elif not repo:
210 elif not repo:
211 msg = _('cannot specify --changelog or --manifest '
211 msg = _('cannot specify --changelog or --manifest '
212 'without a repository')
212 'without a repository')
213 if msg:
213 if msg:
214 raise util.Abort(msg)
214 raise util.Abort(msg)
215
215
216 r = None
216 r = None
217 if repo:
217 if repo:
218 if cl:
218 if cl:
219 r = repo.changelog
219 r = repo.changelog
220 elif mf:
220 elif mf:
221 r = repo.manifest
221 r = repo.manifest
222 elif file_:
222 elif file_:
223 filelog = repo.file(file_)
223 filelog = repo.file(file_)
224 if len(filelog):
224 if len(filelog):
225 r = filelog
225 r = filelog
226 if not r:
226 if not r:
227 if not file_:
227 if not file_:
228 raise error.CommandError(cmd, _('invalid arguments'))
228 raise error.CommandError(cmd, _('invalid arguments'))
229 if not os.path.isfile(file_):
229 if not os.path.isfile(file_):
230 raise util.Abort(_("revlog '%s' not found") % file_)
230 raise util.Abort(_("revlog '%s' not found") % file_)
231 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
231 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
232 file_[:-2] + ".i")
232 file_[:-2] + ".i")
233 return r
233 return r
234
234
235 def copy(ui, repo, pats, opts, rename=False):
235 def copy(ui, repo, pats, opts, rename=False):
236 # called with the repo lock held
236 # called with the repo lock held
237 #
237 #
238 # hgsep => pathname that uses "/" to separate directories
238 # hgsep => pathname that uses "/" to separate directories
239 # ossep => pathname that uses os.sep to separate directories
239 # ossep => pathname that uses os.sep to separate directories
240 cwd = repo.getcwd()
240 cwd = repo.getcwd()
241 targets = {}
241 targets = {}
242 after = opts.get("after")
242 after = opts.get("after")
243 dryrun = opts.get("dry_run")
243 dryrun = opts.get("dry_run")
244 wctx = repo[None]
244 wctx = repo[None]
245
245
246 def walkpat(pat):
246 def walkpat(pat):
247 srcs = []
247 srcs = []
248 badstates = after and '?' or '?r'
248 badstates = after and '?' or '?r'
249 m = scmutil.match(repo[None], [pat], opts, globbed=True)
249 m = scmutil.match(repo[None], [pat], opts, globbed=True)
250 for abs in repo.walk(m):
250 for abs in repo.walk(m):
251 state = repo.dirstate[abs]
251 state = repo.dirstate[abs]
252 rel = m.rel(abs)
252 rel = m.rel(abs)
253 exact = m.exact(abs)
253 exact = m.exact(abs)
254 if state in badstates:
254 if state in badstates:
255 if exact and state == '?':
255 if exact and state == '?':
256 ui.warn(_('%s: not copying - file is not managed\n') % rel)
256 ui.warn(_('%s: not copying - file is not managed\n') % rel)
257 if exact and state == 'r':
257 if exact and state == 'r':
258 ui.warn(_('%s: not copying - file has been marked for'
258 ui.warn(_('%s: not copying - file has been marked for'
259 ' remove\n') % rel)
259 ' remove\n') % rel)
260 continue
260 continue
261 # abs: hgsep
261 # abs: hgsep
262 # rel: ossep
262 # rel: ossep
263 srcs.append((abs, rel, exact))
263 srcs.append((abs, rel, exact))
264 return srcs
264 return srcs
265
265
266 # abssrc: hgsep
266 # abssrc: hgsep
267 # relsrc: ossep
267 # relsrc: ossep
268 # otarget: ossep
268 # otarget: ossep
269 def copyfile(abssrc, relsrc, otarget, exact):
269 def copyfile(abssrc, relsrc, otarget, exact):
270 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
270 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
271 reltarget = repo.pathto(abstarget, cwd)
271 reltarget = repo.pathto(abstarget, cwd)
272 target = repo.wjoin(abstarget)
272 target = repo.wjoin(abstarget)
273 src = repo.wjoin(abssrc)
273 src = repo.wjoin(abssrc)
274 state = repo.dirstate[abstarget]
274 state = repo.dirstate[abstarget]
275
275
276 scmutil.checkportable(ui, abstarget)
276 scmutil.checkportable(ui, abstarget)
277
277
278 # check for collisions
278 # check for collisions
279 prevsrc = targets.get(abstarget)
279 prevsrc = targets.get(abstarget)
280 if prevsrc is not None:
280 if prevsrc is not None:
281 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
281 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
282 (reltarget, repo.pathto(abssrc, cwd),
282 (reltarget, repo.pathto(abssrc, cwd),
283 repo.pathto(prevsrc, cwd)))
283 repo.pathto(prevsrc, cwd)))
284 return
284 return
285
285
286 # check for overwrites
286 # check for overwrites
287 exists = os.path.lexists(target)
287 exists = os.path.lexists(target)
288 samefile = False
288 samefile = False
289 if exists and abssrc != abstarget:
289 if exists and abssrc != abstarget:
290 if (repo.dirstate.normalize(abssrc) ==
290 if (repo.dirstate.normalize(abssrc) ==
291 repo.dirstate.normalize(abstarget)):
291 repo.dirstate.normalize(abstarget)):
292 if not rename:
292 if not rename:
293 ui.warn(_("%s: can't copy - same file\n") % reltarget)
293 ui.warn(_("%s: can't copy - same file\n") % reltarget)
294 return
294 return
295 exists = False
295 exists = False
296 samefile = True
296 samefile = True
297
297
298 if not after and exists or after and state in 'mn':
298 if not after and exists or after and state in 'mn':
299 if not opts['force']:
299 if not opts['force']:
300 ui.warn(_('%s: not overwriting - file exists\n') %
300 ui.warn(_('%s: not overwriting - file exists\n') %
301 reltarget)
301 reltarget)
302 return
302 return
303
303
304 if after:
304 if after:
305 if not exists:
305 if not exists:
306 if rename:
306 if rename:
307 ui.warn(_('%s: not recording move - %s does not exist\n') %
307 ui.warn(_('%s: not recording move - %s does not exist\n') %
308 (relsrc, reltarget))
308 (relsrc, reltarget))
309 else:
309 else:
310 ui.warn(_('%s: not recording copy - %s does not exist\n') %
310 ui.warn(_('%s: not recording copy - %s does not exist\n') %
311 (relsrc, reltarget))
311 (relsrc, reltarget))
312 return
312 return
313 elif not dryrun:
313 elif not dryrun:
314 try:
314 try:
315 if exists:
315 if exists:
316 os.unlink(target)
316 os.unlink(target)
317 targetdir = os.path.dirname(target) or '.'
317 targetdir = os.path.dirname(target) or '.'
318 if not os.path.isdir(targetdir):
318 if not os.path.isdir(targetdir):
319 os.makedirs(targetdir)
319 os.makedirs(targetdir)
320 if samefile:
320 if samefile:
321 tmp = target + "~hgrename"
321 tmp = target + "~hgrename"
322 os.rename(src, tmp)
322 os.rename(src, tmp)
323 os.rename(tmp, target)
323 os.rename(tmp, target)
324 else:
324 else:
325 util.copyfile(src, target)
325 util.copyfile(src, target)
326 srcexists = True
326 srcexists = True
327 except IOError, inst:
327 except IOError, inst:
328 if inst.errno == errno.ENOENT:
328 if inst.errno == errno.ENOENT:
329 ui.warn(_('%s: deleted in working copy\n') % relsrc)
329 ui.warn(_('%s: deleted in working copy\n') % relsrc)
330 srcexists = False
330 srcexists = False
331 else:
331 else:
332 ui.warn(_('%s: cannot copy - %s\n') %
332 ui.warn(_('%s: cannot copy - %s\n') %
333 (relsrc, inst.strerror))
333 (relsrc, inst.strerror))
334 return True # report a failure
334 return True # report a failure
335
335
336 if ui.verbose or not exact:
336 if ui.verbose or not exact:
337 if rename:
337 if rename:
338 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
338 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
339 else:
339 else:
340 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
340 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
341
341
342 targets[abstarget] = abssrc
342 targets[abstarget] = abssrc
343
343
344 # fix up dirstate
344 # fix up dirstate
345 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
345 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
346 dryrun=dryrun, cwd=cwd)
346 dryrun=dryrun, cwd=cwd)
347 if rename and not dryrun:
347 if rename and not dryrun:
348 if not after and srcexists and not samefile:
348 if not after and srcexists and not samefile:
349 util.unlinkpath(repo.wjoin(abssrc))
349 util.unlinkpath(repo.wjoin(abssrc))
350 wctx.forget([abssrc])
350 wctx.forget([abssrc])
351
351
352 # pat: ossep
352 # pat: ossep
353 # dest ossep
353 # dest ossep
354 # srcs: list of (hgsep, hgsep, ossep, bool)
354 # srcs: list of (hgsep, hgsep, ossep, bool)
355 # return: function that takes hgsep and returns ossep
355 # return: function that takes hgsep and returns ossep
356 def targetpathfn(pat, dest, srcs):
356 def targetpathfn(pat, dest, srcs):
357 if os.path.isdir(pat):
357 if os.path.isdir(pat):
358 abspfx = scmutil.canonpath(repo.root, cwd, pat)
358 abspfx = scmutil.canonpath(repo.root, cwd, pat)
359 abspfx = util.localpath(abspfx)
359 abspfx = util.localpath(abspfx)
360 if destdirexists:
360 if destdirexists:
361 striplen = len(os.path.split(abspfx)[0])
361 striplen = len(os.path.split(abspfx)[0])
362 else:
362 else:
363 striplen = len(abspfx)
363 striplen = len(abspfx)
364 if striplen:
364 if striplen:
365 striplen += len(os.sep)
365 striplen += len(os.sep)
366 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
366 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
367 elif destdirexists:
367 elif destdirexists:
368 res = lambda p: os.path.join(dest,
368 res = lambda p: os.path.join(dest,
369 os.path.basename(util.localpath(p)))
369 os.path.basename(util.localpath(p)))
370 else:
370 else:
371 res = lambda p: dest
371 res = lambda p: dest
372 return res
372 return res
373
373
374 # pat: ossep
374 # pat: ossep
375 # dest ossep
375 # dest ossep
376 # srcs: list of (hgsep, hgsep, ossep, bool)
376 # srcs: list of (hgsep, hgsep, ossep, bool)
377 # return: function that takes hgsep and returns ossep
377 # return: function that takes hgsep and returns ossep
378 def targetpathafterfn(pat, dest, srcs):
378 def targetpathafterfn(pat, dest, srcs):
379 if matchmod.patkind(pat):
379 if matchmod.patkind(pat):
380 # a mercurial pattern
380 # a mercurial pattern
381 res = lambda p: os.path.join(dest,
381 res = lambda p: os.path.join(dest,
382 os.path.basename(util.localpath(p)))
382 os.path.basename(util.localpath(p)))
383 else:
383 else:
384 abspfx = scmutil.canonpath(repo.root, cwd, pat)
384 abspfx = scmutil.canonpath(repo.root, cwd, pat)
385 if len(abspfx) < len(srcs[0][0]):
385 if len(abspfx) < len(srcs[0][0]):
386 # A directory. Either the target path contains the last
386 # A directory. Either the target path contains the last
387 # component of the source path or it does not.
387 # component of the source path or it does not.
388 def evalpath(striplen):
388 def evalpath(striplen):
389 score = 0
389 score = 0
390 for s in srcs:
390 for s in srcs:
391 t = os.path.join(dest, util.localpath(s[0])[striplen:])
391 t = os.path.join(dest, util.localpath(s[0])[striplen:])
392 if os.path.lexists(t):
392 if os.path.lexists(t):
393 score += 1
393 score += 1
394 return score
394 return score
395
395
396 abspfx = util.localpath(abspfx)
396 abspfx = util.localpath(abspfx)
397 striplen = len(abspfx)
397 striplen = len(abspfx)
398 if striplen:
398 if striplen:
399 striplen += len(os.sep)
399 striplen += len(os.sep)
400 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
400 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
401 score = evalpath(striplen)
401 score = evalpath(striplen)
402 striplen1 = len(os.path.split(abspfx)[0])
402 striplen1 = len(os.path.split(abspfx)[0])
403 if striplen1:
403 if striplen1:
404 striplen1 += len(os.sep)
404 striplen1 += len(os.sep)
405 if evalpath(striplen1) > score:
405 if evalpath(striplen1) > score:
406 striplen = striplen1
406 striplen = striplen1
407 res = lambda p: os.path.join(dest,
407 res = lambda p: os.path.join(dest,
408 util.localpath(p)[striplen:])
408 util.localpath(p)[striplen:])
409 else:
409 else:
410 # a file
410 # a file
411 if destdirexists:
411 if destdirexists:
412 res = lambda p: os.path.join(dest,
412 res = lambda p: os.path.join(dest,
413 os.path.basename(util.localpath(p)))
413 os.path.basename(util.localpath(p)))
414 else:
414 else:
415 res = lambda p: dest
415 res = lambda p: dest
416 return res
416 return res
417
417
418
418
419 pats = scmutil.expandpats(pats)
419 pats = scmutil.expandpats(pats)
420 if not pats:
420 if not pats:
421 raise util.Abort(_('no source or destination specified'))
421 raise util.Abort(_('no source or destination specified'))
422 if len(pats) == 1:
422 if len(pats) == 1:
423 raise util.Abort(_('no destination specified'))
423 raise util.Abort(_('no destination specified'))
424 dest = pats.pop()
424 dest = pats.pop()
425 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
425 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
426 if not destdirexists:
426 if not destdirexists:
427 if len(pats) > 1 or matchmod.patkind(pats[0]):
427 if len(pats) > 1 or matchmod.patkind(pats[0]):
428 raise util.Abort(_('with multiple sources, destination must be an '
428 raise util.Abort(_('with multiple sources, destination must be an '
429 'existing directory'))
429 'existing directory'))
430 if util.endswithsep(dest):
430 if util.endswithsep(dest):
431 raise util.Abort(_('destination %s is not a directory') % dest)
431 raise util.Abort(_('destination %s is not a directory') % dest)
432
432
433 tfn = targetpathfn
433 tfn = targetpathfn
434 if after:
434 if after:
435 tfn = targetpathafterfn
435 tfn = targetpathafterfn
436 copylist = []
436 copylist = []
437 for pat in pats:
437 for pat in pats:
438 srcs = walkpat(pat)
438 srcs = walkpat(pat)
439 if not srcs:
439 if not srcs:
440 continue
440 continue
441 copylist.append((tfn(pat, dest, srcs), srcs))
441 copylist.append((tfn(pat, dest, srcs), srcs))
442 if not copylist:
442 if not copylist:
443 raise util.Abort(_('no files to copy'))
443 raise util.Abort(_('no files to copy'))
444
444
445 errors = 0
445 errors = 0
446 for targetpath, srcs in copylist:
446 for targetpath, srcs in copylist:
447 for abssrc, relsrc, exact in srcs:
447 for abssrc, relsrc, exact in srcs:
448 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
448 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
449 errors += 1
449 errors += 1
450
450
451 if errors:
451 if errors:
452 ui.warn(_('(consider using --after)\n'))
452 ui.warn(_('(consider using --after)\n'))
453
453
454 return errors != 0
454 return errors != 0
455
455
456 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
456 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
457 runargs=None, appendpid=False):
457 runargs=None, appendpid=False):
458 '''Run a command as a service.'''
458 '''Run a command as a service.'''
459
459
460 if opts['daemon'] and not opts['daemon_pipefds']:
460 if opts['daemon'] and not opts['daemon_pipefds']:
461 # Signal child process startup with file removal
461 # Signal child process startup with file removal
462 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
462 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
463 os.close(lockfd)
463 os.close(lockfd)
464 try:
464 try:
465 if not runargs:
465 if not runargs:
466 runargs = util.hgcmd() + sys.argv[1:]
466 runargs = util.hgcmd() + sys.argv[1:]
467 runargs.append('--daemon-pipefds=%s' % lockpath)
467 runargs.append('--daemon-pipefds=%s' % lockpath)
468 # Don't pass --cwd to the child process, because we've already
468 # Don't pass --cwd to the child process, because we've already
469 # changed directory.
469 # changed directory.
470 for i in xrange(1, len(runargs)):
470 for i in xrange(1, len(runargs)):
471 if runargs[i].startswith('--cwd='):
471 if runargs[i].startswith('--cwd='):
472 del runargs[i]
472 del runargs[i]
473 break
473 break
474 elif runargs[i].startswith('--cwd'):
474 elif runargs[i].startswith('--cwd'):
475 del runargs[i:i + 2]
475 del runargs[i:i + 2]
476 break
476 break
477 def condfn():
477 def condfn():
478 return not os.path.exists(lockpath)
478 return not os.path.exists(lockpath)
479 pid = util.rundetached(runargs, condfn)
479 pid = util.rundetached(runargs, condfn)
480 if pid < 0:
480 if pid < 0:
481 raise util.Abort(_('child process failed to start'))
481 raise util.Abort(_('child process failed to start'))
482 finally:
482 finally:
483 try:
483 try:
484 os.unlink(lockpath)
484 os.unlink(lockpath)
485 except OSError, e:
485 except OSError, e:
486 if e.errno != errno.ENOENT:
486 if e.errno != errno.ENOENT:
487 raise
487 raise
488 if parentfn:
488 if parentfn:
489 return parentfn(pid)
489 return parentfn(pid)
490 else:
490 else:
491 return
491 return
492
492
493 if initfn:
493 if initfn:
494 initfn()
494 initfn()
495
495
496 if opts['pid_file']:
496 if opts['pid_file']:
497 mode = appendpid and 'a' or 'w'
497 mode = appendpid and 'a' or 'w'
498 fp = open(opts['pid_file'], mode)
498 fp = open(opts['pid_file'], mode)
499 fp.write(str(os.getpid()) + '\n')
499 fp.write(str(os.getpid()) + '\n')
500 fp.close()
500 fp.close()
501
501
502 if opts['daemon_pipefds']:
502 if opts['daemon_pipefds']:
503 lockpath = opts['daemon_pipefds']
503 lockpath = opts['daemon_pipefds']
504 try:
504 try:
505 os.setsid()
505 os.setsid()
506 except AttributeError:
506 except AttributeError:
507 pass
507 pass
508 os.unlink(lockpath)
508 os.unlink(lockpath)
509 util.hidewindow()
509 util.hidewindow()
510 sys.stdout.flush()
510 sys.stdout.flush()
511 sys.stderr.flush()
511 sys.stderr.flush()
512
512
513 nullfd = os.open(util.nulldev, os.O_RDWR)
513 nullfd = os.open(util.nulldev, os.O_RDWR)
514 logfilefd = nullfd
514 logfilefd = nullfd
515 if logfile:
515 if logfile:
516 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
516 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
517 os.dup2(nullfd, 0)
517 os.dup2(nullfd, 0)
518 os.dup2(logfilefd, 1)
518 os.dup2(logfilefd, 1)
519 os.dup2(logfilefd, 2)
519 os.dup2(logfilefd, 2)
520 if nullfd not in (0, 1, 2):
520 if nullfd not in (0, 1, 2):
521 os.close(nullfd)
521 os.close(nullfd)
522 if logfile and logfilefd not in (0, 1, 2):
522 if logfile and logfilefd not in (0, 1, 2):
523 os.close(logfilefd)
523 os.close(logfilefd)
524
524
525 if runfn:
525 if runfn:
526 return runfn()
526 return runfn()
527
527
528 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
528 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
529 opts=None):
529 opts=None):
530 '''export changesets as hg patches.'''
530 '''export changesets as hg patches.'''
531
531
532 total = len(revs)
532 total = len(revs)
533 revwidth = max([len(str(rev)) for rev in revs])
533 revwidth = max([len(str(rev)) for rev in revs])
534
534
535 def single(rev, seqno, fp):
535 def single(rev, seqno, fp):
536 ctx = repo[rev]
536 ctx = repo[rev]
537 node = ctx.node()
537 node = ctx.node()
538 parents = [p.node() for p in ctx.parents() if p]
538 parents = [p.node() for p in ctx.parents() if p]
539 branch = ctx.branch()
539 branch = ctx.branch()
540 if switch_parent:
540 if switch_parent:
541 parents.reverse()
541 parents.reverse()
542 prev = (parents and parents[0]) or nullid
542 prev = (parents and parents[0]) or nullid
543
543
544 shouldclose = False
544 shouldclose = False
545 if not fp:
545 if not fp:
546 desc_lines = ctx.description().rstrip().split('\n')
546 desc_lines = ctx.description().rstrip().split('\n')
547 desc = desc_lines[0] #Commit always has a first line.
547 desc = desc_lines[0] #Commit always has a first line.
548 fp = makefileobj(repo, template, node, desc=desc, total=total,
548 fp = makefileobj(repo, template, node, desc=desc, total=total,
549 seqno=seqno, revwidth=revwidth, mode='ab')
549 seqno=seqno, revwidth=revwidth, mode='ab')
550 if fp != template:
550 if fp != template:
551 shouldclose = True
551 shouldclose = True
552 if fp != sys.stdout and util.safehasattr(fp, 'name'):
552 if fp != sys.stdout and util.safehasattr(fp, 'name'):
553 repo.ui.note("%s\n" % fp.name)
553 repo.ui.note("%s\n" % fp.name)
554
554
555 fp.write("# HG changeset patch\n")
555 fp.write("# HG changeset patch\n")
556 fp.write("# User %s\n" % ctx.user())
556 fp.write("# User %s\n" % ctx.user())
557 fp.write("# Date %d %d\n" % ctx.date())
557 fp.write("# Date %d %d\n" % ctx.date())
558 if branch and branch != 'default':
558 if branch and branch != 'default':
559 fp.write("# Branch %s\n" % branch)
559 fp.write("# Branch %s\n" % branch)
560 fp.write("# Node ID %s\n" % hex(node))
560 fp.write("# Node ID %s\n" % hex(node))
561 fp.write("# Parent %s\n" % hex(prev))
561 fp.write("# Parent %s\n" % hex(prev))
562 if len(parents) > 1:
562 if len(parents) > 1:
563 fp.write("# Parent %s\n" % hex(parents[1]))
563 fp.write("# Parent %s\n" % hex(parents[1]))
564 fp.write(ctx.description().rstrip())
564 fp.write(ctx.description().rstrip())
565 fp.write("\n\n")
565 fp.write("\n\n")
566
566
567 for chunk in patch.diff(repo, prev, node, opts=opts):
567 for chunk in patch.diff(repo, prev, node, opts=opts):
568 fp.write(chunk)
568 fp.write(chunk)
569
569
570 if shouldclose:
570 if shouldclose:
571 fp.close()
571 fp.close()
572
572
573 for seqno, rev in enumerate(revs):
573 for seqno, rev in enumerate(revs):
574 single(rev, seqno + 1, fp)
574 single(rev, seqno + 1, fp)
575
575
576 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
576 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
577 changes=None, stat=False, fp=None, prefix='',
577 changes=None, stat=False, fp=None, prefix='',
578 listsubrepos=False):
578 listsubrepos=False):
579 '''show diff or diffstat.'''
579 '''show diff or diffstat.'''
580 if fp is None:
580 if fp is None:
581 write = ui.write
581 write = ui.write
582 else:
582 else:
583 def write(s, **kw):
583 def write(s, **kw):
584 fp.write(s)
584 fp.write(s)
585
585
586 if stat:
586 if stat:
587 diffopts = diffopts.copy(context=0)
587 diffopts = diffopts.copy(context=0)
588 width = 80
588 width = 80
589 if not ui.plain():
589 if not ui.plain():
590 width = ui.termwidth()
590 width = ui.termwidth()
591 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
591 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
592 prefix=prefix)
592 prefix=prefix)
593 for chunk, label in patch.diffstatui(util.iterlines(chunks),
593 for chunk, label in patch.diffstatui(util.iterlines(chunks),
594 width=width,
594 width=width,
595 git=diffopts.git):
595 git=diffopts.git):
596 write(chunk, label=label)
596 write(chunk, label=label)
597 else:
597 else:
598 for chunk, label in patch.diffui(repo, node1, node2, match,
598 for chunk, label in patch.diffui(repo, node1, node2, match,
599 changes, diffopts, prefix=prefix):
599 changes, diffopts, prefix=prefix):
600 write(chunk, label=label)
600 write(chunk, label=label)
601
601
602 if listsubrepos:
602 if listsubrepos:
603 ctx1 = repo[node1]
603 ctx1 = repo[node1]
604 ctx2 = repo[node2]
604 ctx2 = repo[node2]
605 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
605 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
606 tempnode2 = node2
606 tempnode2 = node2
607 try:
607 try:
608 if node2 is not None:
608 if node2 is not None:
609 tempnode2 = ctx2.substate[subpath][1]
609 tempnode2 = ctx2.substate[subpath][1]
610 except KeyError:
610 except KeyError:
611 # A subrepo that existed in node1 was deleted between node1 and
611 # A subrepo that existed in node1 was deleted between node1 and
612 # node2 (inclusive). Thus, ctx2's substate won't contain that
612 # node2 (inclusive). Thus, ctx2's substate won't contain that
613 # subpath. The best we can do is to ignore it.
613 # subpath. The best we can do is to ignore it.
614 tempnode2 = None
614 tempnode2 = None
615 submatch = matchmod.narrowmatcher(subpath, match)
615 submatch = matchmod.narrowmatcher(subpath, match)
616 sub.diff(diffopts, tempnode2, submatch, changes=changes,
616 sub.diff(diffopts, tempnode2, submatch, changes=changes,
617 stat=stat, fp=fp, prefix=prefix)
617 stat=stat, fp=fp, prefix=prefix)
618
618
619 class changeset_printer(object):
619 class changeset_printer(object):
620 '''show changeset information when templating not requested.'''
620 '''show changeset information when templating not requested.'''
621
621
622 def __init__(self, ui, repo, patch, diffopts, buffered):
622 def __init__(self, ui, repo, patch, diffopts, buffered):
623 self.ui = ui
623 self.ui = ui
624 self.repo = repo
624 self.repo = repo
625 self.buffered = buffered
625 self.buffered = buffered
626 self.patch = patch
626 self.patch = patch
627 self.diffopts = diffopts
627 self.diffopts = diffopts
628 self.header = {}
628 self.header = {}
629 self.hunk = {}
629 self.hunk = {}
630 self.lastheader = None
630 self.lastheader = None
631 self.footer = None
631 self.footer = None
632
632
633 def flush(self, rev):
633 def flush(self, rev):
634 if rev in self.header:
634 if rev in self.header:
635 h = self.header[rev]
635 h = self.header[rev]
636 if h != self.lastheader:
636 if h != self.lastheader:
637 self.lastheader = h
637 self.lastheader = h
638 self.ui.write(h)
638 self.ui.write(h)
639 del self.header[rev]
639 del self.header[rev]
640 if rev in self.hunk:
640 if rev in self.hunk:
641 self.ui.write(self.hunk[rev])
641 self.ui.write(self.hunk[rev])
642 del self.hunk[rev]
642 del self.hunk[rev]
643 return 1
643 return 1
644 return 0
644 return 0
645
645
646 def close(self):
646 def close(self):
647 if self.footer:
647 if self.footer:
648 self.ui.write(self.footer)
648 self.ui.write(self.footer)
649
649
650 def show(self, ctx, copies=None, matchfn=None, **props):
650 def show(self, ctx, copies=None, matchfn=None, **props):
651 if self.buffered:
651 if self.buffered:
652 self.ui.pushbuffer()
652 self.ui.pushbuffer()
653 self._show(ctx, copies, matchfn, props)
653 self._show(ctx, copies, matchfn, props)
654 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
654 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
655 else:
655 else:
656 self._show(ctx, copies, matchfn, props)
656 self._show(ctx, copies, matchfn, props)
657
657
658 def _show(self, ctx, copies, matchfn, props):
658 def _show(self, ctx, copies, matchfn, props):
659 '''show a single changeset or file revision'''
659 '''show a single changeset or file revision'''
660 changenode = ctx.node()
660 changenode = ctx.node()
661 rev = ctx.rev()
661 rev = ctx.rev()
662
662
663 if self.ui.quiet:
663 if self.ui.quiet:
664 self.ui.write("%d:%s\n" % (rev, short(changenode)),
664 self.ui.write("%d:%s\n" % (rev, short(changenode)),
665 label='log.node')
665 label='log.node')
666 return
666 return
667
667
668 log = self.repo.changelog
668 log = self.repo.changelog
669 date = util.datestr(ctx.date())
669 date = util.datestr(ctx.date())
670
670
671 hexfunc = self.ui.debugflag and hex or short
671 hexfunc = self.ui.debugflag and hex or short
672
672
673 parents = [(p, hexfunc(log.node(p)))
673 parents = [(p, hexfunc(log.node(p)))
674 for p in self._meaningful_parentrevs(log, rev)]
674 for p in self._meaningful_parentrevs(log, rev)]
675
675
676 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
676 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
677 label='log.changeset')
677 label='log.changeset')
678
678
679 branch = ctx.branch()
679 branch = ctx.branch()
680 # don't show the default branch name
680 # don't show the default branch name
681 if branch != 'default':
681 if branch != 'default':
682 self.ui.write(_("branch: %s\n") % branch,
682 self.ui.write(_("branch: %s\n") % branch,
683 label='log.branch')
683 label='log.branch')
684 for bookmark in self.repo.nodebookmarks(changenode):
684 for bookmark in self.repo.nodebookmarks(changenode):
685 self.ui.write(_("bookmark: %s\n") % bookmark,
685 self.ui.write(_("bookmark: %s\n") % bookmark,
686 label='log.bookmark')
686 label='log.bookmark')
687 for tag in self.repo.nodetags(changenode):
687 for tag in self.repo.nodetags(changenode):
688 self.ui.write(_("tag: %s\n") % tag,
688 self.ui.write(_("tag: %s\n") % tag,
689 label='log.tag')
689 label='log.tag')
690 if self.ui.debugflag and ctx.phase():
690 if self.ui.debugflag and ctx.phase():
691 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
691 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
692 label='log.phase')
692 label='log.phase')
693 for parent in parents:
693 for parent in parents:
694 self.ui.write(_("parent: %d:%s\n") % parent,
694 self.ui.write(_("parent: %d:%s\n") % parent,
695 label='log.parent')
695 label='log.parent')
696
696
697 if self.ui.debugflag:
697 if self.ui.debugflag:
698 mnode = ctx.manifestnode()
698 mnode = ctx.manifestnode()
699 self.ui.write(_("manifest: %d:%s\n") %
699 self.ui.write(_("manifest: %d:%s\n") %
700 (self.repo.manifest.rev(mnode), hex(mnode)),
700 (self.repo.manifest.rev(mnode), hex(mnode)),
701 label='ui.debug log.manifest')
701 label='ui.debug log.manifest')
702 self.ui.write(_("user: %s\n") % ctx.user(),
702 self.ui.write(_("user: %s\n") % ctx.user(),
703 label='log.user')
703 label='log.user')
704 self.ui.write(_("date: %s\n") % date,
704 self.ui.write(_("date: %s\n") % date,
705 label='log.date')
705 label='log.date')
706
706
707 if self.ui.debugflag:
707 if self.ui.debugflag:
708 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
708 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
709 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
709 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
710 files):
710 files):
711 if value:
711 if value:
712 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
712 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
713 label='ui.debug log.files')
713 label='ui.debug log.files')
714 elif ctx.files() and self.ui.verbose:
714 elif ctx.files() and self.ui.verbose:
715 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
715 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
716 label='ui.note log.files')
716 label='ui.note log.files')
717 if copies and self.ui.verbose:
717 if copies and self.ui.verbose:
718 copies = ['%s (%s)' % c for c in copies]
718 copies = ['%s (%s)' % c for c in copies]
719 self.ui.write(_("copies: %s\n") % ' '.join(copies),
719 self.ui.write(_("copies: %s\n") % ' '.join(copies),
720 label='ui.note log.copies')
720 label='ui.note log.copies')
721
721
722 extra = ctx.extra()
722 extra = ctx.extra()
723 if extra and self.ui.debugflag:
723 if extra and self.ui.debugflag:
724 for key, value in sorted(extra.items()):
724 for key, value in sorted(extra.items()):
725 self.ui.write(_("extra: %s=%s\n")
725 self.ui.write(_("extra: %s=%s\n")
726 % (key, value.encode('string_escape')),
726 % (key, value.encode('string_escape')),
727 label='ui.debug log.extra')
727 label='ui.debug log.extra')
728
728
729 description = ctx.description().strip()
729 description = ctx.description().strip()
730 if description:
730 if description:
731 if self.ui.verbose:
731 if self.ui.verbose:
732 self.ui.write(_("description:\n"),
732 self.ui.write(_("description:\n"),
733 label='ui.note log.description')
733 label='ui.note log.description')
734 self.ui.write(description,
734 self.ui.write(description,
735 label='ui.note log.description')
735 label='ui.note log.description')
736 self.ui.write("\n\n")
736 self.ui.write("\n\n")
737 else:
737 else:
738 self.ui.write(_("summary: %s\n") %
738 self.ui.write(_("summary: %s\n") %
739 description.splitlines()[0],
739 description.splitlines()[0],
740 label='log.summary')
740 label='log.summary')
741 self.ui.write("\n")
741 self.ui.write("\n")
742
742
743 self.showpatch(changenode, matchfn)
743 self.showpatch(changenode, matchfn)
744
744
745 def showpatch(self, node, matchfn):
745 def showpatch(self, node, matchfn):
746 if not matchfn:
746 if not matchfn:
747 matchfn = self.patch
747 matchfn = self.patch
748 if matchfn:
748 if matchfn:
749 stat = self.diffopts.get('stat')
749 stat = self.diffopts.get('stat')
750 diff = self.diffopts.get('patch')
750 diff = self.diffopts.get('patch')
751 diffopts = patch.diffopts(self.ui, self.diffopts)
751 diffopts = patch.diffopts(self.ui, self.diffopts)
752 prev = self.repo.changelog.parents(node)[0]
752 prev = self.repo.changelog.parents(node)[0]
753 if stat:
753 if stat:
754 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
754 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
755 match=matchfn, stat=True)
755 match=matchfn, stat=True)
756 if diff:
756 if diff:
757 if stat:
757 if stat:
758 self.ui.write("\n")
758 self.ui.write("\n")
759 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
759 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
760 match=matchfn, stat=False)
760 match=matchfn, stat=False)
761 self.ui.write("\n")
761 self.ui.write("\n")
762
762
763 def _meaningful_parentrevs(self, log, rev):
763 def _meaningful_parentrevs(self, log, rev):
764 """Return list of meaningful (or all if debug) parentrevs for rev.
764 """Return list of meaningful (or all if debug) parentrevs for rev.
765
765
766 For merges (two non-nullrev revisions) both parents are meaningful.
766 For merges (two non-nullrev revisions) both parents are meaningful.
767 Otherwise the first parent revision is considered meaningful if it
767 Otherwise the first parent revision is considered meaningful if it
768 is not the preceding revision.
768 is not the preceding revision.
769 """
769 """
770 parents = log.parentrevs(rev)
770 parents = log.parentrevs(rev)
771 if not self.ui.debugflag and parents[1] == nullrev:
771 if not self.ui.debugflag and parents[1] == nullrev:
772 if parents[0] >= rev - 1:
772 if parents[0] >= rev - 1:
773 parents = []
773 parents = []
774 else:
774 else:
775 parents = [parents[0]]
775 parents = [parents[0]]
776 return parents
776 return parents
777
777
778
778
779 class changeset_templater(changeset_printer):
779 class changeset_templater(changeset_printer):
780 '''format changeset information.'''
780 '''format changeset information.'''
781
781
782 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
782 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
783 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
783 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
784 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
784 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
785 defaulttempl = {
785 defaulttempl = {
786 'parent': '{rev}:{node|formatnode} ',
786 'parent': '{rev}:{node|formatnode} ',
787 'manifest': '{rev}:{node|formatnode}',
787 'manifest': '{rev}:{node|formatnode}',
788 'file_copy': '{name} ({source})',
788 'file_copy': '{name} ({source})',
789 'extra': '{key}={value|stringescape}'
789 'extra': '{key}={value|stringescape}'
790 }
790 }
791 # filecopy is preserved for compatibility reasons
791 # filecopy is preserved for compatibility reasons
792 defaulttempl['filecopy'] = defaulttempl['file_copy']
792 defaulttempl['filecopy'] = defaulttempl['file_copy']
793 self.t = templater.templater(mapfile, {'formatnode': formatnode},
793 self.t = templater.templater(mapfile, {'formatnode': formatnode},
794 cache=defaulttempl)
794 cache=defaulttempl)
795 self.cache = {}
795 self.cache = {}
796
796
797 def use_template(self, t):
797 def use_template(self, t):
798 '''set template string to use'''
798 '''set template string to use'''
799 self.t.cache['changeset'] = t
799 self.t.cache['changeset'] = t
800
800
801 def _meaningful_parentrevs(self, ctx):
801 def _meaningful_parentrevs(self, ctx):
802 """Return list of meaningful (or all if debug) parentrevs for rev.
802 """Return list of meaningful (or all if debug) parentrevs for rev.
803 """
803 """
804 parents = ctx.parents()
804 parents = ctx.parents()
805 if len(parents) > 1:
805 if len(parents) > 1:
806 return parents
806 return parents
807 if self.ui.debugflag:
807 if self.ui.debugflag:
808 return [parents[0], self.repo['null']]
808 return [parents[0], self.repo['null']]
809 if parents[0].rev() >= ctx.rev() - 1:
809 if parents[0].rev() >= ctx.rev() - 1:
810 return []
810 return []
811 return parents
811 return parents
812
812
813 def _show(self, ctx, copies, matchfn, props):
813 def _show(self, ctx, copies, matchfn, props):
814 '''show a single changeset or file revision'''
814 '''show a single changeset or file revision'''
815
815
816 showlist = templatekw.showlist
816 showlist = templatekw.showlist
817
817
818 # showparents() behaviour depends on ui trace level which
818 # showparents() behaviour depends on ui trace level which
819 # causes unexpected behaviours at templating level and makes
819 # causes unexpected behaviours at templating level and makes
820 # it harder to extract it in a standalone function. Its
820 # it harder to extract it in a standalone function. Its
821 # behaviour cannot be changed so leave it here for now.
821 # behaviour cannot be changed so leave it here for now.
822 def showparents(**args):
822 def showparents(**args):
823 ctx = args['ctx']
823 ctx = args['ctx']
824 parents = [[('rev', p.rev()), ('node', p.hex())]
824 parents = [[('rev', p.rev()), ('node', p.hex())]
825 for p in self._meaningful_parentrevs(ctx)]
825 for p in self._meaningful_parentrevs(ctx)]
826 return showlist('parent', parents, **args)
826 return showlist('parent', parents, **args)
827
827
828 props = props.copy()
828 props = props.copy()
829 props.update(templatekw.keywords)
829 props.update(templatekw.keywords)
830 props['parents'] = showparents
830 props['parents'] = showparents
831 props['templ'] = self.t
831 props['templ'] = self.t
832 props['ctx'] = ctx
832 props['ctx'] = ctx
833 props['repo'] = self.repo
833 props['repo'] = self.repo
834 props['revcache'] = {'copies': copies}
834 props['revcache'] = {'copies': copies}
835 props['cache'] = self.cache
835 props['cache'] = self.cache
836
836
837 # find correct templates for current mode
837 # find correct templates for current mode
838
838
839 tmplmodes = [
839 tmplmodes = [
840 (True, None),
840 (True, None),
841 (self.ui.verbose, 'verbose'),
841 (self.ui.verbose, 'verbose'),
842 (self.ui.quiet, 'quiet'),
842 (self.ui.quiet, 'quiet'),
843 (self.ui.debugflag, 'debug'),
843 (self.ui.debugflag, 'debug'),
844 ]
844 ]
845
845
846 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
846 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
847 for mode, postfix in tmplmodes:
847 for mode, postfix in tmplmodes:
848 for type in types:
848 for type in types:
849 cur = postfix and ('%s_%s' % (type, postfix)) or type
849 cur = postfix and ('%s_%s' % (type, postfix)) or type
850 if mode and cur in self.t:
850 if mode and cur in self.t:
851 types[type] = cur
851 types[type] = cur
852
852
853 try:
853 try:
854
854
855 # write header
855 # write header
856 if types['header']:
856 if types['header']:
857 h = templater.stringify(self.t(types['header'], **props))
857 h = templater.stringify(self.t(types['header'], **props))
858 if self.buffered:
858 if self.buffered:
859 self.header[ctx.rev()] = h
859 self.header[ctx.rev()] = h
860 else:
860 else:
861 if self.lastheader != h:
861 if self.lastheader != h:
862 self.lastheader = h
862 self.lastheader = h
863 self.ui.write(h)
863 self.ui.write(h)
864
864
865 # write changeset metadata, then patch if requested
865 # write changeset metadata, then patch if requested
866 key = types['changeset']
866 key = types['changeset']
867 self.ui.write(templater.stringify(self.t(key, **props)))
867 self.ui.write(templater.stringify(self.t(key, **props)))
868 self.showpatch(ctx.node(), matchfn)
868 self.showpatch(ctx.node(), matchfn)
869
869
870 if types['footer']:
870 if types['footer']:
871 if not self.footer:
871 if not self.footer:
872 self.footer = templater.stringify(self.t(types['footer'],
872 self.footer = templater.stringify(self.t(types['footer'],
873 **props))
873 **props))
874
874
875 except KeyError, inst:
875 except KeyError, inst:
876 msg = _("%s: no key named '%s'")
876 msg = _("%s: no key named '%s'")
877 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
877 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
878 except SyntaxError, inst:
878 except SyntaxError, inst:
879 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
879 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
880
880
881 def show_changeset(ui, repo, opts, buffered=False):
881 def show_changeset(ui, repo, opts, buffered=False):
882 """show one changeset using template or regular display.
882 """show one changeset using template or regular display.
883
883
884 Display format will be the first non-empty hit of:
884 Display format will be the first non-empty hit of:
885 1. option 'template'
885 1. option 'template'
886 2. option 'style'
886 2. option 'style'
887 3. [ui] setting 'logtemplate'
887 3. [ui] setting 'logtemplate'
888 4. [ui] setting 'style'
888 4. [ui] setting 'style'
889 If all of these values are either the unset or the empty string,
889 If all of these values are either the unset or the empty string,
890 regular display via changeset_printer() is done.
890 regular display via changeset_printer() is done.
891 """
891 """
892 # options
892 # options
893 patch = False
893 patch = False
894 if opts.get('patch') or opts.get('stat'):
894 if opts.get('patch') or opts.get('stat'):
895 patch = scmutil.matchall(repo)
895 patch = scmutil.matchall(repo)
896
896
897 tmpl = opts.get('template')
897 tmpl = opts.get('template')
898 style = None
898 style = None
899 if tmpl:
899 if tmpl:
900 tmpl = templater.parsestring(tmpl, quoted=False)
900 tmpl = templater.parsestring(tmpl, quoted=False)
901 else:
901 else:
902 style = opts.get('style')
902 style = opts.get('style')
903
903
904 # ui settings
904 # ui settings
905 if not (tmpl or style):
905 if not (tmpl or style):
906 tmpl = ui.config('ui', 'logtemplate')
906 tmpl = ui.config('ui', 'logtemplate')
907 if tmpl:
907 if tmpl:
908 tmpl = templater.parsestring(tmpl)
908 tmpl = templater.parsestring(tmpl)
909 else:
909 else:
910 style = util.expandpath(ui.config('ui', 'style', ''))
910 style = util.expandpath(ui.config('ui', 'style', ''))
911
911
912 if not (tmpl or style):
912 if not (tmpl or style):
913 return changeset_printer(ui, repo, patch, opts, buffered)
913 return changeset_printer(ui, repo, patch, opts, buffered)
914
914
915 mapfile = None
915 mapfile = None
916 if style and not tmpl:
916 if style and not tmpl:
917 mapfile = style
917 mapfile = style
918 if not os.path.split(mapfile)[0]:
918 if not os.path.split(mapfile)[0]:
919 mapname = (templater.templatepath('map-cmdline.' + mapfile)
919 mapname = (templater.templatepath('map-cmdline.' + mapfile)
920 or templater.templatepath(mapfile))
920 or templater.templatepath(mapfile))
921 if mapname:
921 if mapname:
922 mapfile = mapname
922 mapfile = mapname
923
923
924 try:
924 try:
925 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
925 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
926 except SyntaxError, inst:
926 except SyntaxError, inst:
927 raise util.Abort(inst.args[0])
927 raise util.Abort(inst.args[0])
928 if tmpl:
928 if tmpl:
929 t.use_template(tmpl)
929 t.use_template(tmpl)
930 return t
930 return t
931
931
932 def finddate(ui, repo, date):
932 def finddate(ui, repo, date):
933 """Find the tipmost changeset that matches the given date spec"""
933 """Find the tipmost changeset that matches the given date spec"""
934
934
935 df = util.matchdate(date)
935 df = util.matchdate(date)
936 m = scmutil.matchall(repo)
936 m = scmutil.matchall(repo)
937 results = {}
937 results = {}
938
938
939 def prep(ctx, fns):
939 def prep(ctx, fns):
940 d = ctx.date()
940 d = ctx.date()
941 if df(d[0]):
941 if df(d[0]):
942 results[ctx.rev()] = d
942 results[ctx.rev()] = d
943
943
944 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
944 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
945 rev = ctx.rev()
945 rev = ctx.rev()
946 if rev in results:
946 if rev in results:
947 ui.status(_("Found revision %s from %s\n") %
947 ui.status(_("Found revision %s from %s\n") %
948 (rev, util.datestr(results[rev])))
948 (rev, util.datestr(results[rev])))
949 return str(rev)
949 return str(rev)
950
950
951 raise util.Abort(_("revision matching date not found"))
951 raise util.Abort(_("revision matching date not found"))
952
952
953 def walkchangerevs(repo, match, opts, prepare):
953 def walkchangerevs(repo, match, opts, prepare):
954 '''Iterate over files and the revs in which they changed.
954 '''Iterate over files and the revs in which they changed.
955
955
956 Callers most commonly need to iterate backwards over the history
956 Callers most commonly need to iterate backwards over the history
957 in which they are interested. Doing so has awful (quadratic-looking)
957 in which they are interested. Doing so has awful (quadratic-looking)
958 performance, so we use iterators in a "windowed" way.
958 performance, so we use iterators in a "windowed" way.
959
959
960 We walk a window of revisions in the desired order. Within the
960 We walk a window of revisions in the desired order. Within the
961 window, we first walk forwards to gather data, then in the desired
961 window, we first walk forwards to gather data, then in the desired
962 order (usually backwards) to display it.
962 order (usually backwards) to display it.
963
963
964 This function returns an iterator yielding contexts. Before
964 This function returns an iterator yielding contexts. Before
965 yielding each context, the iterator will first call the prepare
965 yielding each context, the iterator will first call the prepare
966 function on each context in the window in forward order.'''
966 function on each context in the window in forward order.'''
967
967
968 def increasing_windows(start, end, windowsize=8, sizelimit=512):
968 def increasing_windows(start, end, windowsize=8, sizelimit=512):
969 if start < end:
969 if start < end:
970 while start < end:
970 while start < end:
971 yield start, min(windowsize, end - start)
971 yield start, min(windowsize, end - start)
972 start += windowsize
972 start += windowsize
973 if windowsize < sizelimit:
973 if windowsize < sizelimit:
974 windowsize *= 2
974 windowsize *= 2
975 else:
975 else:
976 while start > end:
976 while start > end:
977 yield start, min(windowsize, start - end - 1)
977 yield start, min(windowsize, start - end - 1)
978 start -= windowsize
978 start -= windowsize
979 if windowsize < sizelimit:
979 if windowsize < sizelimit:
980 windowsize *= 2
980 windowsize *= 2
981
981
982 follow = opts.get('follow') or opts.get('follow_first')
982 follow = opts.get('follow') or opts.get('follow_first')
983
983
984 if not len(repo):
984 if not len(repo):
985 return []
985 return []
986
986
987 if follow:
987 if follow:
988 defrange = '%s:0' % repo['.'].rev()
988 defrange = '%s:0' % repo['.'].rev()
989 else:
989 else:
990 defrange = '-1:0'
990 defrange = '-1:0'
991 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
991 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
992 if not revs:
992 if not revs:
993 return []
993 return []
994 wanted = set()
994 wanted = set()
995 slowpath = match.anypats() or (match.files() and opts.get('removed'))
995 slowpath = match.anypats() or (match.files() and opts.get('removed'))
996 fncache = {}
996 fncache = {}
997 change = repo.changectx
997 change = repo.changectx
998
998
999 # First step is to fill wanted, the set of revisions that we want to yield.
999 # First step is to fill wanted, the set of revisions that we want to yield.
1000 # When it does not induce extra cost, we also fill fncache for revisions in
1000 # When it does not induce extra cost, we also fill fncache for revisions in
1001 # wanted: a cache of filenames that were changed (ctx.files()) and that
1001 # wanted: a cache of filenames that were changed (ctx.files()) and that
1002 # match the file filtering conditions.
1002 # match the file filtering conditions.
1003
1003
1004 if not slowpath and not match.files():
1004 if not slowpath and not match.files():
1005 # No files, no patterns. Display all revs.
1005 # No files, no patterns. Display all revs.
1006 wanted = set(revs)
1006 wanted = set(revs)
1007 copies = []
1007 copies = []
1008
1008
1009 if not slowpath and match.files():
1009 if not slowpath and match.files():
1010 # We only have to read through the filelog to find wanted revisions
1010 # We only have to read through the filelog to find wanted revisions
1011
1011
1012 minrev, maxrev = min(revs), max(revs)
1012 minrev, maxrev = min(revs), max(revs)
1013 def filerevgen(filelog, last):
1013 def filerevgen(filelog, last):
1014 """
1014 """
1015 Only files, no patterns. Check the history of each file.
1015 Only files, no patterns. Check the history of each file.
1016
1016
1017 Examines filelog entries within minrev, maxrev linkrev range
1017 Examines filelog entries within minrev, maxrev linkrev range
1018 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1018 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1019 tuples in backwards order
1019 tuples in backwards order
1020 """
1020 """
1021 cl_count = len(repo)
1021 cl_count = len(repo)
1022 revs = []
1022 revs = []
1023 for j in xrange(0, last + 1):
1023 for j in xrange(0, last + 1):
1024 linkrev = filelog.linkrev(j)
1024 linkrev = filelog.linkrev(j)
1025 if linkrev < minrev:
1025 if linkrev < minrev:
1026 continue
1026 continue
1027 # only yield rev for which we have the changelog, it can
1027 # only yield rev for which we have the changelog, it can
1028 # happen while doing "hg log" during a pull or commit
1028 # happen while doing "hg log" during a pull or commit
1029 if linkrev >= cl_count:
1029 if linkrev >= cl_count:
1030 break
1030 break
1031
1031
1032 parentlinkrevs = []
1032 parentlinkrevs = []
1033 for p in filelog.parentrevs(j):
1033 for p in filelog.parentrevs(j):
1034 if p != nullrev:
1034 if p != nullrev:
1035 parentlinkrevs.append(filelog.linkrev(p))
1035 parentlinkrevs.append(filelog.linkrev(p))
1036 n = filelog.node(j)
1036 n = filelog.node(j)
1037 revs.append((linkrev, parentlinkrevs,
1037 revs.append((linkrev, parentlinkrevs,
1038 follow and filelog.renamed(n)))
1038 follow and filelog.renamed(n)))
1039
1039
1040 return reversed(revs)
1040 return reversed(revs)
1041 def iterfiles():
1041 def iterfiles():
1042 pctx = repo['.']
1042 pctx = repo['.']
1043 for filename in match.files():
1043 for filename in match.files():
1044 if follow:
1044 if follow:
1045 if filename not in pctx:
1045 if filename not in pctx:
1046 raise util.Abort(_('cannot follow file not in parent '
1046 raise util.Abort(_('cannot follow file not in parent '
1047 'revision: "%s"') % filename)
1047 'revision: "%s"') % filename)
1048 yield filename, pctx[filename].filenode()
1048 yield filename, pctx[filename].filenode()
1049 else:
1049 else:
1050 yield filename, None
1050 yield filename, None
1051 for filename_node in copies:
1051 for filename_node in copies:
1052 yield filename_node
1052 yield filename_node
1053 for file_, node in iterfiles():
1053 for file_, node in iterfiles():
1054 filelog = repo.file(file_)
1054 filelog = repo.file(file_)
1055 if not len(filelog):
1055 if not len(filelog):
1056 if node is None:
1056 if node is None:
1057 # A zero count may be a directory or deleted file, so
1057 # A zero count may be a directory or deleted file, so
1058 # try to find matching entries on the slow path.
1058 # try to find matching entries on the slow path.
1059 if follow:
1059 if follow:
1060 raise util.Abort(
1060 raise util.Abort(
1061 _('cannot follow nonexistent file: "%s"') % file_)
1061 _('cannot follow nonexistent file: "%s"') % file_)
1062 slowpath = True
1062 slowpath = True
1063 break
1063 break
1064 else:
1064 else:
1065 continue
1065 continue
1066
1066
1067 if node is None:
1067 if node is None:
1068 last = len(filelog) - 1
1068 last = len(filelog) - 1
1069 else:
1069 else:
1070 last = filelog.rev(node)
1070 last = filelog.rev(node)
1071
1071
1072
1072
1073 # keep track of all ancestors of the file
1073 # keep track of all ancestors of the file
1074 ancestors = set([filelog.linkrev(last)])
1074 ancestors = set([filelog.linkrev(last)])
1075
1075
1076 # iterate from latest to oldest revision
1076 # iterate from latest to oldest revision
1077 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1077 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1078 if not follow:
1078 if not follow:
1079 if rev > maxrev:
1079 if rev > maxrev:
1080 continue
1080 continue
1081 else:
1081 else:
1082 # Note that last might not be the first interesting
1082 # Note that last might not be the first interesting
1083 # rev to us:
1083 # rev to us:
1084 # if the file has been changed after maxrev, we'll
1084 # if the file has been changed after maxrev, we'll
1085 # have linkrev(last) > maxrev, and we still need
1085 # have linkrev(last) > maxrev, and we still need
1086 # to explore the file graph
1086 # to explore the file graph
1087 if rev not in ancestors:
1087 if rev not in ancestors:
1088 continue
1088 continue
1089 # XXX insert 1327 fix here
1089 # XXX insert 1327 fix here
1090 if flparentlinkrevs:
1090 if flparentlinkrevs:
1091 ancestors.update(flparentlinkrevs)
1091 ancestors.update(flparentlinkrevs)
1092
1092
1093 fncache.setdefault(rev, []).append(file_)
1093 fncache.setdefault(rev, []).append(file_)
1094 wanted.add(rev)
1094 wanted.add(rev)
1095 if copied:
1095 if copied:
1096 copies.append(copied)
1096 copies.append(copied)
1097 if slowpath:
1097 if slowpath:
1098 # We have to read the changelog to match filenames against
1098 # We have to read the changelog to match filenames against
1099 # changed files
1099 # changed files
1100
1100
1101 if follow:
1101 if follow:
1102 raise util.Abort(_('can only follow copies/renames for explicit '
1102 raise util.Abort(_('can only follow copies/renames for explicit '
1103 'filenames'))
1103 'filenames'))
1104
1104
1105 # The slow path checks files modified in every changeset.
1105 # The slow path checks files modified in every changeset.
1106 for i in sorted(revs):
1106 for i in sorted(revs):
1107 ctx = change(i)
1107 ctx = change(i)
1108 matches = filter(match, ctx.files())
1108 matches = filter(match, ctx.files())
1109 if matches:
1109 if matches:
1110 fncache[i] = matches
1110 fncache[i] = matches
1111 wanted.add(i)
1111 wanted.add(i)
1112
1112
1113 class followfilter(object):
1113 class followfilter(object):
1114 def __init__(self, onlyfirst=False):
1114 def __init__(self, onlyfirst=False):
1115 self.startrev = nullrev
1115 self.startrev = nullrev
1116 self.roots = set()
1116 self.roots = set()
1117 self.onlyfirst = onlyfirst
1117 self.onlyfirst = onlyfirst
1118
1118
1119 def match(self, rev):
1119 def match(self, rev):
1120 def realparents(rev):
1120 def realparents(rev):
1121 if self.onlyfirst:
1121 if self.onlyfirst:
1122 return repo.changelog.parentrevs(rev)[0:1]
1122 return repo.changelog.parentrevs(rev)[0:1]
1123 else:
1123 else:
1124 return filter(lambda x: x != nullrev,
1124 return filter(lambda x: x != nullrev,
1125 repo.changelog.parentrevs(rev))
1125 repo.changelog.parentrevs(rev))
1126
1126
1127 if self.startrev == nullrev:
1127 if self.startrev == nullrev:
1128 self.startrev = rev
1128 self.startrev = rev
1129 return True
1129 return True
1130
1130
1131 if rev > self.startrev:
1131 if rev > self.startrev:
1132 # forward: all descendants
1132 # forward: all descendants
1133 if not self.roots:
1133 if not self.roots:
1134 self.roots.add(self.startrev)
1134 self.roots.add(self.startrev)
1135 for parent in realparents(rev):
1135 for parent in realparents(rev):
1136 if parent in self.roots:
1136 if parent in self.roots:
1137 self.roots.add(rev)
1137 self.roots.add(rev)
1138 return True
1138 return True
1139 else:
1139 else:
1140 # backwards: all parents
1140 # backwards: all parents
1141 if not self.roots:
1141 if not self.roots:
1142 self.roots.update(realparents(self.startrev))
1142 self.roots.update(realparents(self.startrev))
1143 if rev in self.roots:
1143 if rev in self.roots:
1144 self.roots.remove(rev)
1144 self.roots.remove(rev)
1145 self.roots.update(realparents(rev))
1145 self.roots.update(realparents(rev))
1146 return True
1146 return True
1147
1147
1148 return False
1148 return False
1149
1149
1150 # it might be worthwhile to do this in the iterator if the rev range
1150 # it might be worthwhile to do this in the iterator if the rev range
1151 # is descending and the prune args are all within that range
1151 # is descending and the prune args are all within that range
1152 for rev in opts.get('prune', ()):
1152 for rev in opts.get('prune', ()):
1153 rev = repo[rev].rev()
1153 rev = repo[rev].rev()
1154 ff = followfilter()
1154 ff = followfilter()
1155 stop = min(revs[0], revs[-1])
1155 stop = min(revs[0], revs[-1])
1156 for x in xrange(rev, stop - 1, -1):
1156 for x in xrange(rev, stop - 1, -1):
1157 if ff.match(x):
1157 if ff.match(x):
1158 wanted.discard(x)
1158 wanted.discard(x)
1159
1159
1160 # Now that wanted is correctly initialized, we can iterate over the
1160 # Now that wanted is correctly initialized, we can iterate over the
1161 # revision range, yielding only revisions in wanted.
1161 # revision range, yielding only revisions in wanted.
1162 def iterate():
1162 def iterate():
1163 if follow and not match.files():
1163 if follow and not match.files():
1164 ff = followfilter(onlyfirst=opts.get('follow_first'))
1164 ff = followfilter(onlyfirst=opts.get('follow_first'))
1165 def want(rev):
1165 def want(rev):
1166 return ff.match(rev) and rev in wanted
1166 return ff.match(rev) and rev in wanted
1167 else:
1167 else:
1168 def want(rev):
1168 def want(rev):
1169 return rev in wanted
1169 return rev in wanted
1170
1170
1171 for i, window in increasing_windows(0, len(revs)):
1171 for i, window in increasing_windows(0, len(revs)):
1172 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1172 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1173 for rev in sorted(nrevs):
1173 for rev in sorted(nrevs):
1174 fns = fncache.get(rev)
1174 fns = fncache.get(rev)
1175 ctx = change(rev)
1175 ctx = change(rev)
1176 if not fns:
1176 if not fns:
1177 def fns_generator():
1177 def fns_generator():
1178 for f in ctx.files():
1178 for f in ctx.files():
1179 if match(f):
1179 if match(f):
1180 yield f
1180 yield f
1181 fns = fns_generator()
1181 fns = fns_generator()
1182 prepare(ctx, fns)
1182 prepare(ctx, fns)
1183 for rev in nrevs:
1183 for rev in nrevs:
1184 yield change(rev)
1184 yield change(rev)
1185 return iterate()
1185 return iterate()
1186
1186
1187 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1187 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1188 join = lambda f: os.path.join(prefix, f)
1188 join = lambda f: os.path.join(prefix, f)
1189 bad = []
1189 bad = []
1190 oldbad = match.bad
1190 oldbad = match.bad
1191 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1191 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1192 names = []
1192 names = []
1193 wctx = repo[None]
1193 wctx = repo[None]
1194 cca = None
1194 cca = None
1195 abort, warn = scmutil.checkportabilityalert(ui)
1195 abort, warn = scmutil.checkportabilityalert(ui)
1196 if abort or warn:
1196 if abort or warn:
1197 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1197 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1198 for f in repo.walk(match):
1198 for f in repo.walk(match):
1199 exact = match.exact(f)
1199 exact = match.exact(f)
1200 if exact or not explicitonly and f not in repo.dirstate:
1200 if exact or not explicitonly and f not in repo.dirstate:
1201 if cca:
1201 if cca:
1202 cca(f)
1202 cca(f)
1203 names.append(f)
1203 names.append(f)
1204 if ui.verbose or not exact:
1204 if ui.verbose or not exact:
1205 ui.status(_('adding %s\n') % match.rel(join(f)))
1205 ui.status(_('adding %s\n') % match.rel(join(f)))
1206
1206
1207 for subpath in wctx.substate:
1207 for subpath in wctx.substate:
1208 sub = wctx.sub(subpath)
1208 sub = wctx.sub(subpath)
1209 try:
1209 try:
1210 submatch = matchmod.narrowmatcher(subpath, match)
1210 submatch = matchmod.narrowmatcher(subpath, match)
1211 if listsubrepos:
1211 if listsubrepos:
1212 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1212 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1213 False))
1213 False))
1214 else:
1214 else:
1215 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1215 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1216 True))
1216 True))
1217 except error.LookupError:
1217 except error.LookupError:
1218 ui.status(_("skipping missing subrepository: %s\n")
1218 ui.status(_("skipping missing subrepository: %s\n")
1219 % join(subpath))
1219 % join(subpath))
1220
1220
1221 if not dryrun:
1221 if not dryrun:
1222 rejected = wctx.add(names, prefix)
1222 rejected = wctx.add(names, prefix)
1223 bad.extend(f for f in rejected if f in match.files())
1223 bad.extend(f for f in rejected if f in match.files())
1224 return bad
1224 return bad
1225
1225
1226 def forget(ui, repo, match, prefix, explicitonly):
1226 def forget(ui, repo, match, prefix, explicitonly):
1227 join = lambda f: os.path.join(prefix, f)
1227 join = lambda f: os.path.join(prefix, f)
1228 bad = []
1228 bad = []
1229 oldbad = match.bad
1229 oldbad = match.bad
1230 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1230 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1231 wctx = repo[None]
1231 wctx = repo[None]
1232 forgot = []
1232 forgot = []
1233 s = repo.status(match=match, clean=True)
1233 s = repo.status(match=match, clean=True)
1234 forget = sorted(s[0] + s[1] + s[3] + s[6])
1234 forget = sorted(s[0] + s[1] + s[3] + s[6])
1235 if explicitonly:
1235 if explicitonly:
1236 forget = [f for f in forget if match.exact(f)]
1236 forget = [f for f in forget if match.exact(f)]
1237
1237
1238 for subpath in wctx.substate:
1238 for subpath in wctx.substate:
1239 sub = wctx.sub(subpath)
1239 sub = wctx.sub(subpath)
1240 try:
1240 try:
1241 submatch = matchmod.narrowmatcher(subpath, match)
1241 submatch = matchmod.narrowmatcher(subpath, match)
1242 subbad, subforgot = sub.forget(ui, submatch, prefix)
1242 subbad, subforgot = sub.forget(ui, submatch, prefix)
1243 bad.extend([subpath + '/' + f for f in subbad])
1243 bad.extend([subpath + '/' + f for f in subbad])
1244 forgot.extend([subpath + '/' + f for f in subforgot])
1244 forgot.extend([subpath + '/' + f for f in subforgot])
1245 except error.LookupError:
1245 except error.LookupError:
1246 ui.status(_("skipping missing subrepository: %s\n")
1246 ui.status(_("skipping missing subrepository: %s\n")
1247 % join(subpath))
1247 % join(subpath))
1248
1248
1249 if not explicitonly:
1249 if not explicitonly:
1250 for f in match.files():
1250 for f in match.files():
1251 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1251 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1252 if f not in forgot:
1252 if f not in forgot:
1253 if os.path.exists(match.rel(join(f))):
1253 if os.path.exists(match.rel(join(f))):
1254 ui.warn(_('not removing %s: '
1254 ui.warn(_('not removing %s: '
1255 'file is already untracked\n')
1255 'file is already untracked\n')
1256 % match.rel(join(f)))
1256 % match.rel(join(f)))
1257 bad.append(f)
1257 bad.append(f)
1258
1258
1259 for f in forget:
1259 for f in forget:
1260 if ui.verbose or not match.exact(f):
1260 if ui.verbose or not match.exact(f):
1261 ui.status(_('removing %s\n') % match.rel(join(f)))
1261 ui.status(_('removing %s\n') % match.rel(join(f)))
1262
1262
1263 rejected = wctx.forget(forget, prefix)
1263 rejected = wctx.forget(forget, prefix)
1264 bad.extend(f for f in rejected if f in match.files())
1264 bad.extend(f for f in rejected if f in match.files())
1265 forgot.extend(forget)
1265 forgot.extend(forget)
1266 return bad, forgot
1266 return bad, forgot
1267
1267
1268 def duplicatecopies(repo, rev, p1):
1268 def duplicatecopies(repo, rev, p1):
1269 "Reproduce copies found in the source revision in the dirstate for grafts"
1269 "Reproduce copies found in the source revision in the dirstate for grafts"
1270 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1270 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1271 repo.dirstate.copy(src, dst)
1271 repo.dirstate.copy(src, dst)
1272
1272
1273 def commit(ui, repo, commitfunc, pats, opts):
1273 def commit(ui, repo, commitfunc, pats, opts):
1274 '''commit the specified files or all outstanding changes'''
1274 '''commit the specified files or all outstanding changes'''
1275 date = opts.get('date')
1275 date = opts.get('date')
1276 if date:
1276 if date:
1277 opts['date'] = util.parsedate(date)
1277 opts['date'] = util.parsedate(date)
1278 message = logmessage(ui, opts)
1278 message = logmessage(ui, opts)
1279
1279
1280 # extract addremove carefully -- this function can be called from a command
1280 # extract addremove carefully -- this function can be called from a command
1281 # that doesn't support addremove
1281 # that doesn't support addremove
1282 if opts.get('addremove'):
1282 if opts.get('addremove'):
1283 scmutil.addremove(repo, pats, opts)
1283 scmutil.addremove(repo, pats, opts)
1284
1284
1285 return commitfunc(ui, repo, message,
1285 return commitfunc(ui, repo, message,
1286 scmutil.match(repo[None], pats, opts), opts)
1286 scmutil.match(repo[None], pats, opts), opts)
1287
1287
1288 def commiteditor(repo, ctx, subs):
1288 def commiteditor(repo, ctx, subs):
1289 if ctx.description():
1289 if ctx.description():
1290 return ctx.description()
1290 return ctx.description()
1291 return commitforceeditor(repo, ctx, subs)
1291 return commitforceeditor(repo, ctx, subs)
1292
1292
1293 def commitforceeditor(repo, ctx, subs):
1293 def commitforceeditor(repo, ctx, subs):
1294 edittext = []
1294 edittext = []
1295 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1295 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1296 if ctx.description():
1296 if ctx.description():
1297 edittext.append(ctx.description())
1297 edittext.append(ctx.description())
1298 edittext.append("")
1298 edittext.append("")
1299 edittext.append("") # Empty line between message and comments.
1299 edittext.append("") # Empty line between message and comments.
1300 edittext.append(_("HG: Enter commit message."
1300 edittext.append(_("HG: Enter commit message."
1301 " Lines beginning with 'HG:' are removed."))
1301 " Lines beginning with 'HG:' are removed."))
1302 edittext.append(_("HG: Leave message empty to abort commit."))
1302 edittext.append(_("HG: Leave message empty to abort commit."))
1303 edittext.append("HG: --")
1303 edittext.append("HG: --")
1304 edittext.append(_("HG: user: %s") % ctx.user())
1304 edittext.append(_("HG: user: %s") % ctx.user())
1305 if ctx.p2():
1305 if ctx.p2():
1306 edittext.append(_("HG: branch merge"))
1306 edittext.append(_("HG: branch merge"))
1307 if ctx.branch():
1307 if ctx.branch():
1308 edittext.append(_("HG: branch '%s'") % ctx.branch())
1308 edittext.append(_("HG: branch '%s'") % ctx.branch())
1309 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1309 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1310 edittext.extend([_("HG: added %s") % f for f in added])
1310 edittext.extend([_("HG: added %s") % f for f in added])
1311 edittext.extend([_("HG: changed %s") % f for f in modified])
1311 edittext.extend([_("HG: changed %s") % f for f in modified])
1312 edittext.extend([_("HG: removed %s") % f for f in removed])
1312 edittext.extend([_("HG: removed %s") % f for f in removed])
1313 if not added and not modified and not removed:
1313 if not added and not modified and not removed:
1314 edittext.append(_("HG: no files changed"))
1314 edittext.append(_("HG: no files changed"))
1315 edittext.append("")
1315 edittext.append("")
1316 # run editor in the repository root
1316 # run editor in the repository root
1317 olddir = os.getcwd()
1317 olddir = os.getcwd()
1318 os.chdir(repo.root)
1318 os.chdir(repo.root)
1319 text = repo.ui.edit("\n".join(edittext), ctx.user())
1319 text = repo.ui.edit("\n".join(edittext), ctx.user())
1320 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1320 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1321 os.chdir(olddir)
1321 os.chdir(olddir)
1322
1322
1323 if not text.strip():
1323 if not text.strip():
1324 raise util.Abort(_("empty commit message"))
1324 raise util.Abort(_("empty commit message"))
1325
1325
1326 return text
1326 return text
1327
1327
1328 def revert(ui, repo, ctx, parents, *pats, **opts):
1328 def revert(ui, repo, ctx, parents, *pats, **opts):
1329 parent, p2 = parents
1329 parent, p2 = parents
1330 node = ctx.node()
1330 node = ctx.node()
1331
1331
1332 mf = ctx.manifest()
1332 mf = ctx.manifest()
1333 if node == parent:
1333 if node == parent:
1334 pmf = mf
1334 pmf = mf
1335 else:
1335 else:
1336 pmf = None
1336 pmf = None
1337
1337
1338 # need all matching names in dirstate and manifest of target rev,
1338 # need all matching names in dirstate and manifest of target rev,
1339 # so have to walk both. do not print errors if files exist in one
1339 # so have to walk both. do not print errors if files exist in one
1340 # but not other.
1340 # but not other.
1341
1341
1342 names = {}
1342 names = {}
1343
1343
1344 wlock = repo.wlock()
1344 wlock = repo.wlock()
1345 try:
1345 try:
1346 # walk dirstate.
1346 # walk dirstate.
1347
1347
1348 m = scmutil.match(repo[None], pats, opts)
1348 m = scmutil.match(repo[None], pats, opts)
1349 m.bad = lambda x, y: False
1349 m.bad = lambda x, y: False
1350 for abs in repo.walk(m):
1350 for abs in repo.walk(m):
1351 names[abs] = m.rel(abs), m.exact(abs)
1351 names[abs] = m.rel(abs), m.exact(abs)
1352
1352
1353 # walk target manifest.
1353 # walk target manifest.
1354
1354
1355 def badfn(path, msg):
1355 def badfn(path, msg):
1356 if path in names:
1356 if path in names:
1357 return
1357 return
1358 if path in repo[node].substate:
1358 if path in repo[node].substate:
1359 ui.warn("%s: %s\n" % (m.rel(path),
1360 'reverting subrepos is unsupported'))
1361 return
1359 return
1362 path_ = path + '/'
1360 path_ = path + '/'
1363 for f in names:
1361 for f in names:
1364 if f.startswith(path_):
1362 if f.startswith(path_):
1365 return
1363 return
1366 ui.warn("%s: %s\n" % (m.rel(path), msg))
1364 ui.warn("%s: %s\n" % (m.rel(path), msg))
1367
1365
1368 m = scmutil.match(repo[node], pats, opts)
1366 m = scmutil.match(repo[node], pats, opts)
1369 m.bad = badfn
1367 m.bad = badfn
1370 for abs in repo[node].walk(m):
1368 for abs in repo[node].walk(m):
1371 if abs not in names:
1369 if abs not in names:
1372 names[abs] = m.rel(abs), m.exact(abs)
1370 names[abs] = m.rel(abs), m.exact(abs)
1373
1371
1372 targetsubs = [s for s in repo[node].substate if m(s)]
1373 if targetsubs and not opts.get('no_backup'):
1374 msg = _("cannot revert subrepos without --no-backup")
1375 raise util.Abort(msg)
1376
1374 m = scmutil.matchfiles(repo, names)
1377 m = scmutil.matchfiles(repo, names)
1375 changes = repo.status(match=m)[:4]
1378 changes = repo.status(match=m)[:4]
1376 modified, added, removed, deleted = map(set, changes)
1379 modified, added, removed, deleted = map(set, changes)
1377
1380
1378 # if f is a rename, also revert the source
1381 # if f is a rename, also revert the source
1379 cwd = repo.getcwd()
1382 cwd = repo.getcwd()
1380 for f in added:
1383 for f in added:
1381 src = repo.dirstate.copied(f)
1384 src = repo.dirstate.copied(f)
1382 if src and src not in names and repo.dirstate[src] == 'r':
1385 if src and src not in names and repo.dirstate[src] == 'r':
1383 removed.add(src)
1386 removed.add(src)
1384 names[src] = (repo.pathto(src, cwd), True)
1387 names[src] = (repo.pathto(src, cwd), True)
1385
1388
1386 def removeforget(abs):
1389 def removeforget(abs):
1387 if repo.dirstate[abs] == 'a':
1390 if repo.dirstate[abs] == 'a':
1388 return _('forgetting %s\n')
1391 return _('forgetting %s\n')
1389 return _('removing %s\n')
1392 return _('removing %s\n')
1390
1393
1391 revert = ([], _('reverting %s\n'))
1394 revert = ([], _('reverting %s\n'))
1392 add = ([], _('adding %s\n'))
1395 add = ([], _('adding %s\n'))
1393 remove = ([], removeforget)
1396 remove = ([], removeforget)
1394 undelete = ([], _('undeleting %s\n'))
1397 undelete = ([], _('undeleting %s\n'))
1395
1398
1396 disptable = (
1399 disptable = (
1397 # dispatch table:
1400 # dispatch table:
1398 # file state
1401 # file state
1399 # action if in target manifest
1402 # action if in target manifest
1400 # action if not in target manifest
1403 # action if not in target manifest
1401 # make backup if in target manifest
1404 # make backup if in target manifest
1402 # make backup if not in target manifest
1405 # make backup if not in target manifest
1403 (modified, revert, remove, True, True),
1406 (modified, revert, remove, True, True),
1404 (added, revert, remove, True, False),
1407 (added, revert, remove, True, False),
1405 (removed, undelete, None, False, False),
1408 (removed, undelete, None, False, False),
1406 (deleted, revert, remove, False, False),
1409 (deleted, revert, remove, False, False),
1407 )
1410 )
1408
1411
1409 for abs, (rel, exact) in sorted(names.items()):
1412 for abs, (rel, exact) in sorted(names.items()):
1410 mfentry = mf.get(abs)
1413 mfentry = mf.get(abs)
1411 target = repo.wjoin(abs)
1414 target = repo.wjoin(abs)
1412 def handle(xlist, dobackup):
1415 def handle(xlist, dobackup):
1413 xlist[0].append(abs)
1416 xlist[0].append(abs)
1414 if (dobackup and not opts.get('no_backup') and
1417 if (dobackup and not opts.get('no_backup') and
1415 os.path.lexists(target)):
1418 os.path.lexists(target)):
1416 bakname = "%s.orig" % rel
1419 bakname = "%s.orig" % rel
1417 ui.note(_('saving current version of %s as %s\n') %
1420 ui.note(_('saving current version of %s as %s\n') %
1418 (rel, bakname))
1421 (rel, bakname))
1419 if not opts.get('dry_run'):
1422 if not opts.get('dry_run'):
1420 util.rename(target, bakname)
1423 util.rename(target, bakname)
1421 if ui.verbose or not exact:
1424 if ui.verbose or not exact:
1422 msg = xlist[1]
1425 msg = xlist[1]
1423 if not isinstance(msg, basestring):
1426 if not isinstance(msg, basestring):
1424 msg = msg(abs)
1427 msg = msg(abs)
1425 ui.status(msg % rel)
1428 ui.status(msg % rel)
1426 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1429 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1427 if abs not in table:
1430 if abs not in table:
1428 continue
1431 continue
1429 # file has changed in dirstate
1432 # file has changed in dirstate
1430 if mfentry:
1433 if mfentry:
1431 handle(hitlist, backuphit)
1434 handle(hitlist, backuphit)
1432 elif misslist is not None:
1435 elif misslist is not None:
1433 handle(misslist, backupmiss)
1436 handle(misslist, backupmiss)
1434 break
1437 break
1435 else:
1438 else:
1436 if abs not in repo.dirstate:
1439 if abs not in repo.dirstate:
1437 if mfentry:
1440 if mfentry:
1438 handle(add, True)
1441 handle(add, True)
1439 elif exact:
1442 elif exact:
1440 ui.warn(_('file not managed: %s\n') % rel)
1443 ui.warn(_('file not managed: %s\n') % rel)
1441 continue
1444 continue
1442 # file has not changed in dirstate
1445 # file has not changed in dirstate
1443 if node == parent:
1446 if node == parent:
1444 if exact:
1447 if exact:
1445 ui.warn(_('no changes needed to %s\n') % rel)
1448 ui.warn(_('no changes needed to %s\n') % rel)
1446 continue
1449 continue
1447 if pmf is None:
1450 if pmf is None:
1448 # only need parent manifest in this unlikely case,
1451 # only need parent manifest in this unlikely case,
1449 # so do not read by default
1452 # so do not read by default
1450 pmf = repo[parent].manifest()
1453 pmf = repo[parent].manifest()
1451 if abs in pmf and mfentry:
1454 if abs in pmf and mfentry:
1452 # if version of file is same in parent and target
1455 # if version of file is same in parent and target
1453 # manifests, do nothing
1456 # manifests, do nothing
1454 if (pmf[abs] != mfentry or
1457 if (pmf[abs] != mfentry or
1455 pmf.flags(abs) != mf.flags(abs)):
1458 pmf.flags(abs) != mf.flags(abs)):
1456 handle(revert, False)
1459 handle(revert, False)
1457 else:
1460 else:
1458 handle(remove, False)
1461 handle(remove, False)
1459
1462
1460 if not opts.get('dry_run'):
1463 if not opts.get('dry_run'):
1461 def checkout(f):
1464 def checkout(f):
1462 fc = ctx[f]
1465 fc = ctx[f]
1463 repo.wwrite(f, fc.data(), fc.flags())
1466 repo.wwrite(f, fc.data(), fc.flags())
1464
1467
1465 audit_path = scmutil.pathauditor(repo.root)
1468 audit_path = scmutil.pathauditor(repo.root)
1466 for f in remove[0]:
1469 for f in remove[0]:
1467 if repo.dirstate[f] == 'a':
1470 if repo.dirstate[f] == 'a':
1468 repo.dirstate.drop(f)
1471 repo.dirstate.drop(f)
1469 continue
1472 continue
1470 audit_path(f)
1473 audit_path(f)
1471 try:
1474 try:
1472 util.unlinkpath(repo.wjoin(f))
1475 util.unlinkpath(repo.wjoin(f))
1473 except OSError:
1476 except OSError:
1474 pass
1477 pass
1475 repo.dirstate.remove(f)
1478 repo.dirstate.remove(f)
1476
1479
1477 normal = None
1480 normal = None
1478 if node == parent:
1481 if node == parent:
1479 # We're reverting to our parent. If possible, we'd like status
1482 # We're reverting to our parent. If possible, we'd like status
1480 # to report the file as clean. We have to use normallookup for
1483 # to report the file as clean. We have to use normallookup for
1481 # merges to avoid losing information about merged/dirty files.
1484 # merges to avoid losing information about merged/dirty files.
1482 if p2 != nullid:
1485 if p2 != nullid:
1483 normal = repo.dirstate.normallookup
1486 normal = repo.dirstate.normallookup
1484 else:
1487 else:
1485 normal = repo.dirstate.normal
1488 normal = repo.dirstate.normal
1486 for f in revert[0]:
1489 for f in revert[0]:
1487 checkout(f)
1490 checkout(f)
1488 if normal:
1491 if normal:
1489 normal(f)
1492 normal(f)
1490
1493
1491 for f in add[0]:
1494 for f in add[0]:
1492 checkout(f)
1495 checkout(f)
1493 repo.dirstate.add(f)
1496 repo.dirstate.add(f)
1494
1497
1495 normal = repo.dirstate.normallookup
1498 normal = repo.dirstate.normallookup
1496 if node == parent and p2 == nullid:
1499 if node == parent and p2 == nullid:
1497 normal = repo.dirstate.normal
1500 normal = repo.dirstate.normal
1498 for f in undelete[0]:
1501 for f in undelete[0]:
1499 checkout(f)
1502 checkout(f)
1500 normal(f)
1503 normal(f)
1501
1504
1505 if targetsubs:
1506 # Revert the subrepos on the revert list
1507 for sub in targetsubs:
1508 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
1502 finally:
1509 finally:
1503 wlock.release()
1510 wlock.release()
1504
1511
1505 def command(table):
1512 def command(table):
1506 '''returns a function object bound to table which can be used as
1513 '''returns a function object bound to table which can be used as
1507 a decorator for populating table as a command table'''
1514 a decorator for populating table as a command table'''
1508
1515
1509 def cmd(name, options, synopsis=None):
1516 def cmd(name, options, synopsis=None):
1510 def decorator(func):
1517 def decorator(func):
1511 if synopsis:
1518 if synopsis:
1512 table[name] = func, options[:], synopsis
1519 table[name] = func, options[:], synopsis
1513 else:
1520 else:
1514 table[name] = func, options[:]
1521 table[name] = func, options[:]
1515 return func
1522 return func
1516 return decorator
1523 return decorator
1517
1524
1518 return cmd
1525 return cmd
@@ -1,1163 +1,1174
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 errno, os, re, xml.dom.minidom, shutil, posixpath
8 import errno, os, re, xml.dom.minidom, shutil, posixpath
9 import stat, subprocess, tarfile
9 import stat, subprocess, tarfile
10 from i18n import _
10 from i18n import _
11 import config, scmutil, util, node, error, cmdutil, bookmarks
11 import config, scmutil, util, node, error, cmdutil, bookmarks
12 hg = None
12 hg = None
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14
14
15 nullstate = ('', '', 'empty')
15 nullstate = ('', '', 'empty')
16
16
17 def state(ctx, ui):
17 def state(ctx, ui):
18 """return a state dict, mapping subrepo paths configured in .hgsub
18 """return a state dict, mapping subrepo paths configured in .hgsub
19 to tuple: (source from .hgsub, revision from .hgsubstate, kind
19 to tuple: (source from .hgsub, revision from .hgsubstate, kind
20 (key in types dict))
20 (key in types dict))
21 """
21 """
22 p = config.config()
22 p = config.config()
23 def read(f, sections=None, remap=None):
23 def read(f, sections=None, remap=None):
24 if f in ctx:
24 if f in ctx:
25 try:
25 try:
26 data = ctx[f].data()
26 data = ctx[f].data()
27 except IOError, err:
27 except IOError, err:
28 if err.errno != errno.ENOENT:
28 if err.errno != errno.ENOENT:
29 raise
29 raise
30 # handle missing subrepo spec files as removed
30 # handle missing subrepo spec files as removed
31 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
31 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
32 return
32 return
33 p.parse(f, data, sections, remap, read)
33 p.parse(f, data, sections, remap, read)
34 else:
34 else:
35 raise util.Abort(_("subrepo spec file %s not found") % f)
35 raise util.Abort(_("subrepo spec file %s not found") % f)
36
36
37 if '.hgsub' in ctx:
37 if '.hgsub' in ctx:
38 read('.hgsub')
38 read('.hgsub')
39
39
40 for path, src in ui.configitems('subpaths'):
40 for path, src in ui.configitems('subpaths'):
41 p.set('subpaths', path, src, ui.configsource('subpaths', path))
41 p.set('subpaths', path, src, ui.configsource('subpaths', path))
42
42
43 rev = {}
43 rev = {}
44 if '.hgsubstate' in ctx:
44 if '.hgsubstate' in ctx:
45 try:
45 try:
46 for l in ctx['.hgsubstate'].data().splitlines():
46 for l in ctx['.hgsubstate'].data().splitlines():
47 revision, path = l.split(" ", 1)
47 revision, path = l.split(" ", 1)
48 rev[path] = revision
48 rev[path] = revision
49 except IOError, err:
49 except IOError, err:
50 if err.errno != errno.ENOENT:
50 if err.errno != errno.ENOENT:
51 raise
51 raise
52
52
53 def remap(src):
53 def remap(src):
54 for pattern, repl in p.items('subpaths'):
54 for pattern, repl in p.items('subpaths'):
55 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
55 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
56 # does a string decode.
56 # does a string decode.
57 repl = repl.encode('string-escape')
57 repl = repl.encode('string-escape')
58 # However, we still want to allow back references to go
58 # However, we still want to allow back references to go
59 # through unharmed, so we turn r'\\1' into r'\1'. Again,
59 # through unharmed, so we turn r'\\1' into r'\1'. Again,
60 # extra escapes are needed because re.sub string decodes.
60 # extra escapes are needed because re.sub string decodes.
61 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
61 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
62 try:
62 try:
63 src = re.sub(pattern, repl, src, 1)
63 src = re.sub(pattern, repl, src, 1)
64 except re.error, e:
64 except re.error, e:
65 raise util.Abort(_("bad subrepository pattern in %s: %s")
65 raise util.Abort(_("bad subrepository pattern in %s: %s")
66 % (p.source('subpaths', pattern), e))
66 % (p.source('subpaths', pattern), e))
67 return src
67 return src
68
68
69 state = {}
69 state = {}
70 for path, src in p[''].items():
70 for path, src in p[''].items():
71 kind = 'hg'
71 kind = 'hg'
72 if src.startswith('['):
72 if src.startswith('['):
73 if ']' not in src:
73 if ']' not in src:
74 raise util.Abort(_('missing ] in subrepo source'))
74 raise util.Abort(_('missing ] in subrepo source'))
75 kind, src = src.split(']', 1)
75 kind, src = src.split(']', 1)
76 kind = kind[1:]
76 kind = kind[1:]
77 src = src.lstrip() # strip any extra whitespace after ']'
77 src = src.lstrip() # strip any extra whitespace after ']'
78
78
79 if not util.url(src).isabs():
79 if not util.url(src).isabs():
80 parent = _abssource(ctx._repo, abort=False)
80 parent = _abssource(ctx._repo, abort=False)
81 if parent:
81 if parent:
82 parent = util.url(parent)
82 parent = util.url(parent)
83 parent.path = posixpath.join(parent.path or '', src)
83 parent.path = posixpath.join(parent.path or '', src)
84 parent.path = posixpath.normpath(parent.path)
84 parent.path = posixpath.normpath(parent.path)
85 joined = str(parent)
85 joined = str(parent)
86 # Remap the full joined path and use it if it changes,
86 # Remap the full joined path and use it if it changes,
87 # else remap the original source.
87 # else remap the original source.
88 remapped = remap(joined)
88 remapped = remap(joined)
89 if remapped == joined:
89 if remapped == joined:
90 src = remap(src)
90 src = remap(src)
91 else:
91 else:
92 src = remapped
92 src = remapped
93
93
94 src = remap(src)
94 src = remap(src)
95 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
95 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
96
96
97 return state
97 return state
98
98
99 def writestate(repo, state):
99 def writestate(repo, state):
100 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
100 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
101 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
101 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
102 repo.wwrite('.hgsubstate', ''.join(lines), '')
102 repo.wwrite('.hgsubstate', ''.join(lines), '')
103
103
104 def submerge(repo, wctx, mctx, actx, overwrite):
104 def submerge(repo, wctx, mctx, actx, overwrite):
105 """delegated from merge.applyupdates: merging of .hgsubstate file
105 """delegated from merge.applyupdates: merging of .hgsubstate file
106 in working context, merging context and ancestor context"""
106 in working context, merging context and ancestor context"""
107 if mctx == actx: # backwards?
107 if mctx == actx: # backwards?
108 actx = wctx.p1()
108 actx = wctx.p1()
109 s1 = wctx.substate
109 s1 = wctx.substate
110 s2 = mctx.substate
110 s2 = mctx.substate
111 sa = actx.substate
111 sa = actx.substate
112 sm = {}
112 sm = {}
113
113
114 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
114 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
115
115
116 def debug(s, msg, r=""):
116 def debug(s, msg, r=""):
117 if r:
117 if r:
118 r = "%s:%s:%s" % r
118 r = "%s:%s:%s" % r
119 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
119 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
120
120
121 for s, l in s1.items():
121 for s, l in s1.items():
122 a = sa.get(s, nullstate)
122 a = sa.get(s, nullstate)
123 ld = l # local state with possible dirty flag for compares
123 ld = l # local state with possible dirty flag for compares
124 if wctx.sub(s).dirty():
124 if wctx.sub(s).dirty():
125 ld = (l[0], l[1] + "+")
125 ld = (l[0], l[1] + "+")
126 if wctx == actx: # overwrite
126 if wctx == actx: # overwrite
127 a = ld
127 a = ld
128
128
129 if s in s2:
129 if s in s2:
130 r = s2[s]
130 r = s2[s]
131 if ld == r or r == a: # no change or local is newer
131 if ld == r or r == a: # no change or local is newer
132 sm[s] = l
132 sm[s] = l
133 continue
133 continue
134 elif ld == a: # other side changed
134 elif ld == a: # other side changed
135 debug(s, "other changed, get", r)
135 debug(s, "other changed, get", r)
136 wctx.sub(s).get(r, overwrite)
136 wctx.sub(s).get(r, overwrite)
137 sm[s] = r
137 sm[s] = r
138 elif ld[0] != r[0]: # sources differ
138 elif ld[0] != r[0]: # sources differ
139 if repo.ui.promptchoice(
139 if repo.ui.promptchoice(
140 _(' subrepository sources for %s differ\n'
140 _(' subrepository sources for %s differ\n'
141 'use (l)ocal source (%s) or (r)emote source (%s)?')
141 'use (l)ocal source (%s) or (r)emote source (%s)?')
142 % (s, l[0], r[0]),
142 % (s, l[0], r[0]),
143 (_('&Local'), _('&Remote')), 0):
143 (_('&Local'), _('&Remote')), 0):
144 debug(s, "prompt changed, get", r)
144 debug(s, "prompt changed, get", r)
145 wctx.sub(s).get(r, overwrite)
145 wctx.sub(s).get(r, overwrite)
146 sm[s] = r
146 sm[s] = r
147 elif ld[1] == a[1]: # local side is unchanged
147 elif ld[1] == a[1]: # local side is unchanged
148 debug(s, "other side changed, get", r)
148 debug(s, "other side changed, get", r)
149 wctx.sub(s).get(r, overwrite)
149 wctx.sub(s).get(r, overwrite)
150 sm[s] = r
150 sm[s] = r
151 else:
151 else:
152 debug(s, "both sides changed, merge with", r)
152 debug(s, "both sides changed, merge with", r)
153 wctx.sub(s).merge(r)
153 wctx.sub(s).merge(r)
154 sm[s] = l
154 sm[s] = l
155 elif ld == a: # remote removed, local unchanged
155 elif ld == a: # remote removed, local unchanged
156 debug(s, "remote removed, remove")
156 debug(s, "remote removed, remove")
157 wctx.sub(s).remove()
157 wctx.sub(s).remove()
158 elif a == nullstate: # not present in remote or ancestor
158 elif a == nullstate: # not present in remote or ancestor
159 debug(s, "local added, keep")
159 debug(s, "local added, keep")
160 sm[s] = l
160 sm[s] = l
161 continue
161 continue
162 else:
162 else:
163 if repo.ui.promptchoice(
163 if repo.ui.promptchoice(
164 _(' local changed subrepository %s which remote removed\n'
164 _(' local changed subrepository %s which remote removed\n'
165 'use (c)hanged version or (d)elete?') % s,
165 'use (c)hanged version or (d)elete?') % s,
166 (_('&Changed'), _('&Delete')), 0):
166 (_('&Changed'), _('&Delete')), 0):
167 debug(s, "prompt remove")
167 debug(s, "prompt remove")
168 wctx.sub(s).remove()
168 wctx.sub(s).remove()
169
169
170 for s, r in sorted(s2.items()):
170 for s, r in sorted(s2.items()):
171 if s in s1:
171 if s in s1:
172 continue
172 continue
173 elif s not in sa:
173 elif s not in sa:
174 debug(s, "remote added, get", r)
174 debug(s, "remote added, get", r)
175 mctx.sub(s).get(r)
175 mctx.sub(s).get(r)
176 sm[s] = r
176 sm[s] = r
177 elif r != sa[s]:
177 elif r != sa[s]:
178 if repo.ui.promptchoice(
178 if repo.ui.promptchoice(
179 _(' remote changed subrepository %s which local removed\n'
179 _(' remote changed subrepository %s which local removed\n'
180 'use (c)hanged version or (d)elete?') % s,
180 'use (c)hanged version or (d)elete?') % s,
181 (_('&Changed'), _('&Delete')), 0) == 0:
181 (_('&Changed'), _('&Delete')), 0) == 0:
182 debug(s, "prompt recreate", r)
182 debug(s, "prompt recreate", r)
183 wctx.sub(s).get(r)
183 wctx.sub(s).get(r)
184 sm[s] = r
184 sm[s] = r
185
185
186 # record merged .hgsubstate
186 # record merged .hgsubstate
187 writestate(repo, sm)
187 writestate(repo, sm)
188
188
189 def _updateprompt(ui, sub, dirty, local, remote):
189 def _updateprompt(ui, sub, dirty, local, remote):
190 if dirty:
190 if dirty:
191 msg = (_(' subrepository sources for %s differ\n'
191 msg = (_(' subrepository sources for %s differ\n'
192 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
192 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
193 % (subrelpath(sub), local, remote))
193 % (subrelpath(sub), local, remote))
194 else:
194 else:
195 msg = (_(' subrepository sources for %s differ (in checked out version)\n'
195 msg = (_(' subrepository sources for %s differ (in checked out version)\n'
196 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
196 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
197 % (subrelpath(sub), local, remote))
197 % (subrelpath(sub), local, remote))
198 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
198 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
199
199
200 def reporelpath(repo):
200 def reporelpath(repo):
201 """return path to this (sub)repo as seen from outermost repo"""
201 """return path to this (sub)repo as seen from outermost repo"""
202 parent = repo
202 parent = repo
203 while util.safehasattr(parent, '_subparent'):
203 while util.safehasattr(parent, '_subparent'):
204 parent = parent._subparent
204 parent = parent._subparent
205 p = parent.root.rstrip(os.sep)
205 p = parent.root.rstrip(os.sep)
206 return repo.root[len(p) + 1:]
206 return repo.root[len(p) + 1:]
207
207
208 def subrelpath(sub):
208 def subrelpath(sub):
209 """return path to this subrepo as seen from outermost repo"""
209 """return path to this subrepo as seen from outermost repo"""
210 if util.safehasattr(sub, '_relpath'):
210 if util.safehasattr(sub, '_relpath'):
211 return sub._relpath
211 return sub._relpath
212 if not util.safehasattr(sub, '_repo'):
212 if not util.safehasattr(sub, '_repo'):
213 return sub._path
213 return sub._path
214 return reporelpath(sub._repo)
214 return reporelpath(sub._repo)
215
215
216 def _abssource(repo, push=False, abort=True):
216 def _abssource(repo, push=False, abort=True):
217 """return pull/push path of repo - either based on parent repo .hgsub info
217 """return pull/push path of repo - either based on parent repo .hgsub info
218 or on the top repo config. Abort or return None if no source found."""
218 or on the top repo config. Abort or return None if no source found."""
219 if util.safehasattr(repo, '_subparent'):
219 if util.safehasattr(repo, '_subparent'):
220 source = util.url(repo._subsource)
220 source = util.url(repo._subsource)
221 if source.isabs():
221 if source.isabs():
222 return str(source)
222 return str(source)
223 source.path = posixpath.normpath(source.path)
223 source.path = posixpath.normpath(source.path)
224 parent = _abssource(repo._subparent, push, abort=False)
224 parent = _abssource(repo._subparent, push, abort=False)
225 if parent:
225 if parent:
226 parent = util.url(util.pconvert(parent))
226 parent = util.url(util.pconvert(parent))
227 parent.path = posixpath.join(parent.path or '', source.path)
227 parent.path = posixpath.join(parent.path or '', source.path)
228 parent.path = posixpath.normpath(parent.path)
228 parent.path = posixpath.normpath(parent.path)
229 return str(parent)
229 return str(parent)
230 else: # recursion reached top repo
230 else: # recursion reached top repo
231 if util.safehasattr(repo, '_subtoppath'):
231 if util.safehasattr(repo, '_subtoppath'):
232 return repo._subtoppath
232 return repo._subtoppath
233 if push and repo.ui.config('paths', 'default-push'):
233 if push and repo.ui.config('paths', 'default-push'):
234 return repo.ui.config('paths', 'default-push')
234 return repo.ui.config('paths', 'default-push')
235 if repo.ui.config('paths', 'default'):
235 if repo.ui.config('paths', 'default'):
236 return repo.ui.config('paths', 'default')
236 return repo.ui.config('paths', 'default')
237 if abort:
237 if abort:
238 raise util.Abort(_("default path for subrepository %s not found") %
238 raise util.Abort(_("default path for subrepository %s not found") %
239 reporelpath(repo))
239 reporelpath(repo))
240
240
241 def itersubrepos(ctx1, ctx2):
241 def itersubrepos(ctx1, ctx2):
242 """find subrepos in ctx1 or ctx2"""
242 """find subrepos in ctx1 or ctx2"""
243 # Create a (subpath, ctx) mapping where we prefer subpaths from
243 # Create a (subpath, ctx) mapping where we prefer subpaths from
244 # ctx1. The subpaths from ctx2 are important when the .hgsub file
244 # ctx1. The subpaths from ctx2 are important when the .hgsub file
245 # has been modified (in ctx2) but not yet committed (in ctx1).
245 # has been modified (in ctx2) but not yet committed (in ctx1).
246 subpaths = dict.fromkeys(ctx2.substate, ctx2)
246 subpaths = dict.fromkeys(ctx2.substate, ctx2)
247 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
247 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
248 for subpath, ctx in sorted(subpaths.iteritems()):
248 for subpath, ctx in sorted(subpaths.iteritems()):
249 yield subpath, ctx.sub(subpath)
249 yield subpath, ctx.sub(subpath)
250
250
251 def subrepo(ctx, path):
251 def subrepo(ctx, path):
252 """return instance of the right subrepo class for subrepo in path"""
252 """return instance of the right subrepo class for subrepo in path"""
253 # subrepo inherently violates our import layering rules
253 # subrepo inherently violates our import layering rules
254 # because it wants to make repo objects from deep inside the stack
254 # because it wants to make repo objects from deep inside the stack
255 # so we manually delay the circular imports to not break
255 # so we manually delay the circular imports to not break
256 # scripts that don't use our demand-loading
256 # scripts that don't use our demand-loading
257 global hg
257 global hg
258 import hg as h
258 import hg as h
259 hg = h
259 hg = h
260
260
261 scmutil.pathauditor(ctx._repo.root)(path)
261 scmutil.pathauditor(ctx._repo.root)(path)
262 state = ctx.substate.get(path, nullstate)
262 state = ctx.substate.get(path, nullstate)
263 if state[2] not in types:
263 if state[2] not in types:
264 raise util.Abort(_('unknown subrepo type %s') % state[2])
264 raise util.Abort(_('unknown subrepo type %s') % state[2])
265 return types[state[2]](ctx, path, state[:2])
265 return types[state[2]](ctx, path, state[:2])
266
266
267 # subrepo classes need to implement the following abstract class:
267 # subrepo classes need to implement the following abstract class:
268
268
269 class abstractsubrepo(object):
269 class abstractsubrepo(object):
270
270
271 def dirty(self, ignoreupdate=False):
271 def dirty(self, ignoreupdate=False):
272 """returns true if the dirstate of the subrepo is dirty or does not
272 """returns true if the dirstate of the subrepo is dirty or does not
273 match current stored state. If ignoreupdate is true, only check
273 match current stored state. If ignoreupdate is true, only check
274 whether the subrepo has uncommitted changes in its dirstate.
274 whether the subrepo has uncommitted changes in its dirstate.
275 """
275 """
276 raise NotImplementedError
276 raise NotImplementedError
277
277
278 def basestate(self):
278 def basestate(self):
279 """current working directory base state, disregarding .hgsubstate
279 """current working directory base state, disregarding .hgsubstate
280 state and working directory modifications"""
280 state and working directory modifications"""
281 raise NotImplementedError
281 raise NotImplementedError
282
282
283 def checknested(self, path):
283 def checknested(self, path):
284 """check if path is a subrepository within this repository"""
284 """check if path is a subrepository within this repository"""
285 return False
285 return False
286
286
287 def commit(self, text, user, date):
287 def commit(self, text, user, date):
288 """commit the current changes to the subrepo with the given
288 """commit the current changes to the subrepo with the given
289 log message. Use given user and date if possible. Return the
289 log message. Use given user and date if possible. Return the
290 new state of the subrepo.
290 new state of the subrepo.
291 """
291 """
292 raise NotImplementedError
292 raise NotImplementedError
293
293
294 def remove(self):
294 def remove(self):
295 """remove the subrepo
295 """remove the subrepo
296
296
297 (should verify the dirstate is not dirty first)
297 (should verify the dirstate is not dirty first)
298 """
298 """
299 raise NotImplementedError
299 raise NotImplementedError
300
300
301 def get(self, state, overwrite=False):
301 def get(self, state, overwrite=False):
302 """run whatever commands are needed to put the subrepo into
302 """run whatever commands are needed to put the subrepo into
303 this state
303 this state
304 """
304 """
305 raise NotImplementedError
305 raise NotImplementedError
306
306
307 def merge(self, state):
307 def merge(self, state):
308 """merge currently-saved state with the new state."""
308 """merge currently-saved state with the new state."""
309 raise NotImplementedError
309 raise NotImplementedError
310
310
311 def push(self, opts):
311 def push(self, opts):
312 """perform whatever action is analogous to 'hg push'
312 """perform whatever action is analogous to 'hg push'
313
313
314 This may be a no-op on some systems.
314 This may be a no-op on some systems.
315 """
315 """
316 raise NotImplementedError
316 raise NotImplementedError
317
317
318 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
318 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
319 return []
319 return []
320
320
321 def status(self, rev2, **opts):
321 def status(self, rev2, **opts):
322 return [], [], [], [], [], [], []
322 return [], [], [], [], [], [], []
323
323
324 def diff(self, diffopts, node2, match, prefix, **opts):
324 def diff(self, diffopts, node2, match, prefix, **opts):
325 pass
325 pass
326
326
327 def outgoing(self, ui, dest, opts):
327 def outgoing(self, ui, dest, opts):
328 return 1
328 return 1
329
329
330 def incoming(self, ui, source, opts):
330 def incoming(self, ui, source, opts):
331 return 1
331 return 1
332
332
333 def files(self):
333 def files(self):
334 """return filename iterator"""
334 """return filename iterator"""
335 raise NotImplementedError
335 raise NotImplementedError
336
336
337 def filedata(self, name):
337 def filedata(self, name):
338 """return file data"""
338 """return file data"""
339 raise NotImplementedError
339 raise NotImplementedError
340
340
341 def fileflags(self, name):
341 def fileflags(self, name):
342 """return file flags"""
342 """return file flags"""
343 return ''
343 return ''
344
344
345 def archive(self, ui, archiver, prefix):
345 def archive(self, ui, archiver, prefix):
346 files = self.files()
346 files = self.files()
347 total = len(files)
347 total = len(files)
348 relpath = subrelpath(self)
348 relpath = subrelpath(self)
349 ui.progress(_('archiving (%s)') % relpath, 0,
349 ui.progress(_('archiving (%s)') % relpath, 0,
350 unit=_('files'), total=total)
350 unit=_('files'), total=total)
351 for i, name in enumerate(files):
351 for i, name in enumerate(files):
352 flags = self.fileflags(name)
352 flags = self.fileflags(name)
353 mode = 'x' in flags and 0755 or 0644
353 mode = 'x' in flags and 0755 or 0644
354 symlink = 'l' in flags
354 symlink = 'l' in flags
355 archiver.addfile(os.path.join(prefix, self._path, name),
355 archiver.addfile(os.path.join(prefix, self._path, name),
356 mode, symlink, self.filedata(name))
356 mode, symlink, self.filedata(name))
357 ui.progress(_('archiving (%s)') % relpath, i + 1,
357 ui.progress(_('archiving (%s)') % relpath, i + 1,
358 unit=_('files'), total=total)
358 unit=_('files'), total=total)
359 ui.progress(_('archiving (%s)') % relpath, None)
359 ui.progress(_('archiving (%s)') % relpath, None)
360
360
361 def walk(self, match):
361 def walk(self, match):
362 '''
362 '''
363 walk recursively through the directory tree, finding all files
363 walk recursively through the directory tree, finding all files
364 matched by the match function
364 matched by the match function
365 '''
365 '''
366 pass
366 pass
367
367
368 def forget(self, ui, match, prefix):
368 def forget(self, ui, match, prefix):
369 return []
369 return []
370
370
371 def revert(self, ui, substate, *pats, **opts):
372 return []
373
371 class hgsubrepo(abstractsubrepo):
374 class hgsubrepo(abstractsubrepo):
372 def __init__(self, ctx, path, state):
375 def __init__(self, ctx, path, state):
373 self._path = path
376 self._path = path
374 self._state = state
377 self._state = state
375 r = ctx._repo
378 r = ctx._repo
376 root = r.wjoin(path)
379 root = r.wjoin(path)
377 create = False
380 create = False
378 if not os.path.exists(os.path.join(root, '.hg')):
381 if not os.path.exists(os.path.join(root, '.hg')):
379 create = True
382 create = True
380 util.makedirs(root)
383 util.makedirs(root)
381 self._repo = hg.repository(r.ui, root, create=create)
384 self._repo = hg.repository(r.ui, root, create=create)
382 self._initrepo(r, state[0], create)
385 self._initrepo(r, state[0], create)
383
386
384 def _initrepo(self, parentrepo, source, create):
387 def _initrepo(self, parentrepo, source, create):
385 self._repo._subparent = parentrepo
388 self._repo._subparent = parentrepo
386 self._repo._subsource = source
389 self._repo._subsource = source
387
390
388 if create:
391 if create:
389 fp = self._repo.opener("hgrc", "w", text=True)
392 fp = self._repo.opener("hgrc", "w", text=True)
390 fp.write('[paths]\n')
393 fp.write('[paths]\n')
391
394
392 def addpathconfig(key, value):
395 def addpathconfig(key, value):
393 if value:
396 if value:
394 fp.write('%s = %s\n' % (key, value))
397 fp.write('%s = %s\n' % (key, value))
395 self._repo.ui.setconfig('paths', key, value)
398 self._repo.ui.setconfig('paths', key, value)
396
399
397 defpath = _abssource(self._repo, abort=False)
400 defpath = _abssource(self._repo, abort=False)
398 defpushpath = _abssource(self._repo, True, abort=False)
401 defpushpath = _abssource(self._repo, True, abort=False)
399 addpathconfig('default', defpath)
402 addpathconfig('default', defpath)
400 if defpath != defpushpath:
403 if defpath != defpushpath:
401 addpathconfig('default-push', defpushpath)
404 addpathconfig('default-push', defpushpath)
402 fp.close()
405 fp.close()
403
406
404 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
407 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
405 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
408 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
406 os.path.join(prefix, self._path), explicitonly)
409 os.path.join(prefix, self._path), explicitonly)
407
410
408 def status(self, rev2, **opts):
411 def status(self, rev2, **opts):
409 try:
412 try:
410 rev1 = self._state[1]
413 rev1 = self._state[1]
411 ctx1 = self._repo[rev1]
414 ctx1 = self._repo[rev1]
412 ctx2 = self._repo[rev2]
415 ctx2 = self._repo[rev2]
413 return self._repo.status(ctx1, ctx2, **opts)
416 return self._repo.status(ctx1, ctx2, **opts)
414 except error.RepoLookupError, inst:
417 except error.RepoLookupError, inst:
415 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
418 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
416 % (inst, subrelpath(self)))
419 % (inst, subrelpath(self)))
417 return [], [], [], [], [], [], []
420 return [], [], [], [], [], [], []
418
421
419 def diff(self, diffopts, node2, match, prefix, **opts):
422 def diff(self, diffopts, node2, match, prefix, **opts):
420 try:
423 try:
421 node1 = node.bin(self._state[1])
424 node1 = node.bin(self._state[1])
422 # We currently expect node2 to come from substate and be
425 # We currently expect node2 to come from substate and be
423 # in hex format
426 # in hex format
424 if node2 is not None:
427 if node2 is not None:
425 node2 = node.bin(node2)
428 node2 = node.bin(node2)
426 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
429 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
427 node1, node2, match,
430 node1, node2, match,
428 prefix=os.path.join(prefix, self._path),
431 prefix=os.path.join(prefix, self._path),
429 listsubrepos=True, **opts)
432 listsubrepos=True, **opts)
430 except error.RepoLookupError, inst:
433 except error.RepoLookupError, inst:
431 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
434 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
432 % (inst, subrelpath(self)))
435 % (inst, subrelpath(self)))
433
436
434 def archive(self, ui, archiver, prefix):
437 def archive(self, ui, archiver, prefix):
435 self._get(self._state + ('hg',))
438 self._get(self._state + ('hg',))
436 abstractsubrepo.archive(self, ui, archiver, prefix)
439 abstractsubrepo.archive(self, ui, archiver, prefix)
437
440
438 rev = self._state[1]
441 rev = self._state[1]
439 ctx = self._repo[rev]
442 ctx = self._repo[rev]
440 for subpath in ctx.substate:
443 for subpath in ctx.substate:
441 s = subrepo(ctx, subpath)
444 s = subrepo(ctx, subpath)
442 s.archive(ui, archiver, os.path.join(prefix, self._path))
445 s.archive(ui, archiver, os.path.join(prefix, self._path))
443
446
444 def dirty(self, ignoreupdate=False):
447 def dirty(self, ignoreupdate=False):
445 r = self._state[1]
448 r = self._state[1]
446 if r == '' and not ignoreupdate: # no state recorded
449 if r == '' and not ignoreupdate: # no state recorded
447 return True
450 return True
448 w = self._repo[None]
451 w = self._repo[None]
449 if r != w.p1().hex() and not ignoreupdate:
452 if r != w.p1().hex() and not ignoreupdate:
450 # different version checked out
453 # different version checked out
451 return True
454 return True
452 return w.dirty() # working directory changed
455 return w.dirty() # working directory changed
453
456
454 def basestate(self):
457 def basestate(self):
455 return self._repo['.'].hex()
458 return self._repo['.'].hex()
456
459
457 def checknested(self, path):
460 def checknested(self, path):
458 return self._repo._checknested(self._repo.wjoin(path))
461 return self._repo._checknested(self._repo.wjoin(path))
459
462
460 def commit(self, text, user, date):
463 def commit(self, text, user, date):
461 # don't bother committing in the subrepo if it's only been
464 # don't bother committing in the subrepo if it's only been
462 # updated
465 # updated
463 if not self.dirty(True):
466 if not self.dirty(True):
464 return self._repo['.'].hex()
467 return self._repo['.'].hex()
465 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
468 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
466 n = self._repo.commit(text, user, date)
469 n = self._repo.commit(text, user, date)
467 if not n:
470 if not n:
468 return self._repo['.'].hex() # different version checked out
471 return self._repo['.'].hex() # different version checked out
469 return node.hex(n)
472 return node.hex(n)
470
473
471 def remove(self):
474 def remove(self):
472 # we can't fully delete the repository as it may contain
475 # we can't fully delete the repository as it may contain
473 # local-only history
476 # local-only history
474 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
477 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
475 hg.clean(self._repo, node.nullid, False)
478 hg.clean(self._repo, node.nullid, False)
476
479
477 def _get(self, state):
480 def _get(self, state):
478 source, revision, kind = state
481 source, revision, kind = state
479 if revision not in self._repo:
482 if revision not in self._repo:
480 self._repo._subsource = source
483 self._repo._subsource = source
481 srcurl = _abssource(self._repo)
484 srcurl = _abssource(self._repo)
482 other = hg.peer(self._repo.ui, {}, srcurl)
485 other = hg.peer(self._repo.ui, {}, srcurl)
483 if len(self._repo) == 0:
486 if len(self._repo) == 0:
484 self._repo.ui.status(_('cloning subrepo %s from %s\n')
487 self._repo.ui.status(_('cloning subrepo %s from %s\n')
485 % (subrelpath(self), srcurl))
488 % (subrelpath(self), srcurl))
486 parentrepo = self._repo._subparent
489 parentrepo = self._repo._subparent
487 shutil.rmtree(self._repo.path)
490 shutil.rmtree(self._repo.path)
488 other, self._repo = hg.clone(self._repo._subparent.ui, {}, other,
491 other, self._repo = hg.clone(self._repo._subparent.ui, {}, other,
489 self._repo.root, update=False)
492 self._repo.root, update=False)
490 self._initrepo(parentrepo, source, create=True)
493 self._initrepo(parentrepo, source, create=True)
491 else:
494 else:
492 self._repo.ui.status(_('pulling subrepo %s from %s\n')
495 self._repo.ui.status(_('pulling subrepo %s from %s\n')
493 % (subrelpath(self), srcurl))
496 % (subrelpath(self), srcurl))
494 self._repo.pull(other)
497 self._repo.pull(other)
495 bookmarks.updatefromremote(self._repo.ui, self._repo, other,
498 bookmarks.updatefromremote(self._repo.ui, self._repo, other,
496 srcurl)
499 srcurl)
497
500
498 def get(self, state, overwrite=False):
501 def get(self, state, overwrite=False):
499 self._get(state)
502 self._get(state)
500 source, revision, kind = state
503 source, revision, kind = state
501 self._repo.ui.debug("getting subrepo %s\n" % self._path)
504 self._repo.ui.debug("getting subrepo %s\n" % self._path)
502 hg.clean(self._repo, revision, False)
505 hg.clean(self._repo, revision, False)
503
506
504 def merge(self, state):
507 def merge(self, state):
505 self._get(state)
508 self._get(state)
506 cur = self._repo['.']
509 cur = self._repo['.']
507 dst = self._repo[state[1]]
510 dst = self._repo[state[1]]
508 anc = dst.ancestor(cur)
511 anc = dst.ancestor(cur)
509
512
510 def mergefunc():
513 def mergefunc():
511 if anc == cur and dst.branch() == cur.branch():
514 if anc == cur and dst.branch() == cur.branch():
512 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
515 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
513 hg.update(self._repo, state[1])
516 hg.update(self._repo, state[1])
514 elif anc == dst:
517 elif anc == dst:
515 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
518 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
516 else:
519 else:
517 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
520 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
518 hg.merge(self._repo, state[1], remind=False)
521 hg.merge(self._repo, state[1], remind=False)
519
522
520 wctx = self._repo[None]
523 wctx = self._repo[None]
521 if self.dirty():
524 if self.dirty():
522 if anc != dst:
525 if anc != dst:
523 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
526 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
524 mergefunc()
527 mergefunc()
525 else:
528 else:
526 mergefunc()
529 mergefunc()
527 else:
530 else:
528 mergefunc()
531 mergefunc()
529
532
530 def push(self, opts):
533 def push(self, opts):
531 force = opts.get('force')
534 force = opts.get('force')
532 newbranch = opts.get('new_branch')
535 newbranch = opts.get('new_branch')
533 ssh = opts.get('ssh')
536 ssh = opts.get('ssh')
534
537
535 # push subrepos depth-first for coherent ordering
538 # push subrepos depth-first for coherent ordering
536 c = self._repo['']
539 c = self._repo['']
537 subs = c.substate # only repos that are committed
540 subs = c.substate # only repos that are committed
538 for s in sorted(subs):
541 for s in sorted(subs):
539 if c.sub(s).push(opts) == 0:
542 if c.sub(s).push(opts) == 0:
540 return False
543 return False
541
544
542 dsturl = _abssource(self._repo, True)
545 dsturl = _abssource(self._repo, True)
543 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
546 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
544 (subrelpath(self), dsturl))
547 (subrelpath(self), dsturl))
545 other = hg.peer(self._repo.ui, {'ssh': ssh}, dsturl)
548 other = hg.peer(self._repo.ui, {'ssh': ssh}, dsturl)
546 return self._repo.push(other, force, newbranch=newbranch)
549 return self._repo.push(other, force, newbranch=newbranch)
547
550
548 def outgoing(self, ui, dest, opts):
551 def outgoing(self, ui, dest, opts):
549 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
552 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
550
553
551 def incoming(self, ui, source, opts):
554 def incoming(self, ui, source, opts):
552 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
555 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
553
556
554 def files(self):
557 def files(self):
555 rev = self._state[1]
558 rev = self._state[1]
556 ctx = self._repo[rev]
559 ctx = self._repo[rev]
557 return ctx.manifest()
560 return ctx.manifest()
558
561
559 def filedata(self, name):
562 def filedata(self, name):
560 rev = self._state[1]
563 rev = self._state[1]
561 return self._repo[rev][name].data()
564 return self._repo[rev][name].data()
562
565
563 def fileflags(self, name):
566 def fileflags(self, name):
564 rev = self._state[1]
567 rev = self._state[1]
565 ctx = self._repo[rev]
568 ctx = self._repo[rev]
566 return ctx.flags(name)
569 return ctx.flags(name)
567
570
568 def walk(self, match):
571 def walk(self, match):
569 ctx = self._repo[None]
572 ctx = self._repo[None]
570 return ctx.walk(match)
573 return ctx.walk(match)
571
574
572 def forget(self, ui, match, prefix):
575 def forget(self, ui, match, prefix):
573 return cmdutil.forget(ui, self._repo, match,
576 return cmdutil.forget(ui, self._repo, match,
574 os.path.join(prefix, self._path), True)
577 os.path.join(prefix, self._path), True)
575
578
579 def revert(self, ui, substate, *pats, **opts):
580 # reverting a subrepo is done by updating it to the revision
581 # specified in the corresponding substate dictionary
582 ui.status(_('reverting subrepo %s\n') % substate[0])
583
584 # Update the repo to the revision specified in the given substate
585 self.get(substate, overwrite=True)
586
576 class svnsubrepo(abstractsubrepo):
587 class svnsubrepo(abstractsubrepo):
577 def __init__(self, ctx, path, state):
588 def __init__(self, ctx, path, state):
578 self._path = path
589 self._path = path
579 self._state = state
590 self._state = state
580 self._ctx = ctx
591 self._ctx = ctx
581 self._ui = ctx._repo.ui
592 self._ui = ctx._repo.ui
582 self._exe = util.findexe('svn')
593 self._exe = util.findexe('svn')
583 if not self._exe:
594 if not self._exe:
584 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
595 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
585 % self._path)
596 % self._path)
586
597
587 def _svncommand(self, commands, filename='', failok=False):
598 def _svncommand(self, commands, filename='', failok=False):
588 cmd = [self._exe]
599 cmd = [self._exe]
589 extrakw = {}
600 extrakw = {}
590 if not self._ui.interactive():
601 if not self._ui.interactive():
591 # Making stdin be a pipe should prevent svn from behaving
602 # Making stdin be a pipe should prevent svn from behaving
592 # interactively even if we can't pass --non-interactive.
603 # interactively even if we can't pass --non-interactive.
593 extrakw['stdin'] = subprocess.PIPE
604 extrakw['stdin'] = subprocess.PIPE
594 # Starting in svn 1.5 --non-interactive is a global flag
605 # Starting in svn 1.5 --non-interactive is a global flag
595 # instead of being per-command, but we need to support 1.4 so
606 # instead of being per-command, but we need to support 1.4 so
596 # we have to be intelligent about what commands take
607 # we have to be intelligent about what commands take
597 # --non-interactive.
608 # --non-interactive.
598 if commands[0] in ('update', 'checkout', 'commit'):
609 if commands[0] in ('update', 'checkout', 'commit'):
599 cmd.append('--non-interactive')
610 cmd.append('--non-interactive')
600 cmd.extend(commands)
611 cmd.extend(commands)
601 if filename is not None:
612 if filename is not None:
602 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
613 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
603 cmd.append(path)
614 cmd.append(path)
604 env = dict(os.environ)
615 env = dict(os.environ)
605 # Avoid localized output, preserve current locale for everything else.
616 # Avoid localized output, preserve current locale for everything else.
606 env['LC_MESSAGES'] = 'C'
617 env['LC_MESSAGES'] = 'C'
607 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
618 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
608 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
619 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
609 universal_newlines=True, env=env, **extrakw)
620 universal_newlines=True, env=env, **extrakw)
610 stdout, stderr = p.communicate()
621 stdout, stderr = p.communicate()
611 stderr = stderr.strip()
622 stderr = stderr.strip()
612 if not failok:
623 if not failok:
613 if p.returncode:
624 if p.returncode:
614 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
625 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
615 if stderr:
626 if stderr:
616 self._ui.warn(stderr + '\n')
627 self._ui.warn(stderr + '\n')
617 return stdout, stderr
628 return stdout, stderr
618
629
619 @propertycache
630 @propertycache
620 def _svnversion(self):
631 def _svnversion(self):
621 output, err = self._svncommand(['--version'], filename=None)
632 output, err = self._svncommand(['--version'], filename=None)
622 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
633 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
623 if not m:
634 if not m:
624 raise util.Abort(_('cannot retrieve svn tool version'))
635 raise util.Abort(_('cannot retrieve svn tool version'))
625 return (int(m.group(1)), int(m.group(2)))
636 return (int(m.group(1)), int(m.group(2)))
626
637
627 def _wcrevs(self):
638 def _wcrevs(self):
628 # Get the working directory revision as well as the last
639 # Get the working directory revision as well as the last
629 # commit revision so we can compare the subrepo state with
640 # commit revision so we can compare the subrepo state with
630 # both. We used to store the working directory one.
641 # both. We used to store the working directory one.
631 output, err = self._svncommand(['info', '--xml'])
642 output, err = self._svncommand(['info', '--xml'])
632 doc = xml.dom.minidom.parseString(output)
643 doc = xml.dom.minidom.parseString(output)
633 entries = doc.getElementsByTagName('entry')
644 entries = doc.getElementsByTagName('entry')
634 lastrev, rev = '0', '0'
645 lastrev, rev = '0', '0'
635 if entries:
646 if entries:
636 rev = str(entries[0].getAttribute('revision')) or '0'
647 rev = str(entries[0].getAttribute('revision')) or '0'
637 commits = entries[0].getElementsByTagName('commit')
648 commits = entries[0].getElementsByTagName('commit')
638 if commits:
649 if commits:
639 lastrev = str(commits[0].getAttribute('revision')) or '0'
650 lastrev = str(commits[0].getAttribute('revision')) or '0'
640 return (lastrev, rev)
651 return (lastrev, rev)
641
652
642 def _wcrev(self):
653 def _wcrev(self):
643 return self._wcrevs()[0]
654 return self._wcrevs()[0]
644
655
645 def _wcchanged(self):
656 def _wcchanged(self):
646 """Return (changes, extchanges) where changes is True
657 """Return (changes, extchanges) where changes is True
647 if the working directory was changed, and extchanges is
658 if the working directory was changed, and extchanges is
648 True if any of these changes concern an external entry.
659 True if any of these changes concern an external entry.
649 """
660 """
650 output, err = self._svncommand(['status', '--xml'])
661 output, err = self._svncommand(['status', '--xml'])
651 externals, changes = [], []
662 externals, changes = [], []
652 doc = xml.dom.minidom.parseString(output)
663 doc = xml.dom.minidom.parseString(output)
653 for e in doc.getElementsByTagName('entry'):
664 for e in doc.getElementsByTagName('entry'):
654 s = e.getElementsByTagName('wc-status')
665 s = e.getElementsByTagName('wc-status')
655 if not s:
666 if not s:
656 continue
667 continue
657 item = s[0].getAttribute('item')
668 item = s[0].getAttribute('item')
658 props = s[0].getAttribute('props')
669 props = s[0].getAttribute('props')
659 path = e.getAttribute('path')
670 path = e.getAttribute('path')
660 if item == 'external':
671 if item == 'external':
661 externals.append(path)
672 externals.append(path)
662 if (item not in ('', 'normal', 'unversioned', 'external')
673 if (item not in ('', 'normal', 'unversioned', 'external')
663 or props not in ('', 'none', 'normal')):
674 or props not in ('', 'none', 'normal')):
664 changes.append(path)
675 changes.append(path)
665 for path in changes:
676 for path in changes:
666 for ext in externals:
677 for ext in externals:
667 if path == ext or path.startswith(ext + os.sep):
678 if path == ext or path.startswith(ext + os.sep):
668 return True, True
679 return True, True
669 return bool(changes), False
680 return bool(changes), False
670
681
671 def dirty(self, ignoreupdate=False):
682 def dirty(self, ignoreupdate=False):
672 if not self._wcchanged()[0]:
683 if not self._wcchanged()[0]:
673 if self._state[1] in self._wcrevs() or ignoreupdate:
684 if self._state[1] in self._wcrevs() or ignoreupdate:
674 return False
685 return False
675 return True
686 return True
676
687
677 def basestate(self):
688 def basestate(self):
678 return self._wcrev()
689 return self._wcrev()
679
690
680 def commit(self, text, user, date):
691 def commit(self, text, user, date):
681 # user and date are out of our hands since svn is centralized
692 # user and date are out of our hands since svn is centralized
682 changed, extchanged = self._wcchanged()
693 changed, extchanged = self._wcchanged()
683 if not changed:
694 if not changed:
684 return self._wcrev()
695 return self._wcrev()
685 if extchanged:
696 if extchanged:
686 # Do not try to commit externals
697 # Do not try to commit externals
687 raise util.Abort(_('cannot commit svn externals'))
698 raise util.Abort(_('cannot commit svn externals'))
688 commitinfo, err = self._svncommand(['commit', '-m', text])
699 commitinfo, err = self._svncommand(['commit', '-m', text])
689 self._ui.status(commitinfo)
700 self._ui.status(commitinfo)
690 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
701 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
691 if not newrev:
702 if not newrev:
692 raise util.Abort(commitinfo.splitlines()[-1])
703 raise util.Abort(commitinfo.splitlines()[-1])
693 newrev = newrev.groups()[0]
704 newrev = newrev.groups()[0]
694 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
705 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
695 return newrev
706 return newrev
696
707
697 def remove(self):
708 def remove(self):
698 if self.dirty():
709 if self.dirty():
699 self._ui.warn(_('not removing repo %s because '
710 self._ui.warn(_('not removing repo %s because '
700 'it has changes.\n' % self._path))
711 'it has changes.\n' % self._path))
701 return
712 return
702 self._ui.note(_('removing subrepo %s\n') % self._path)
713 self._ui.note(_('removing subrepo %s\n') % self._path)
703
714
704 def onerror(function, path, excinfo):
715 def onerror(function, path, excinfo):
705 if function is not os.remove:
716 if function is not os.remove:
706 raise
717 raise
707 # read-only files cannot be unlinked under Windows
718 # read-only files cannot be unlinked under Windows
708 s = os.stat(path)
719 s = os.stat(path)
709 if (s.st_mode & stat.S_IWRITE) != 0:
720 if (s.st_mode & stat.S_IWRITE) != 0:
710 raise
721 raise
711 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
722 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
712 os.remove(path)
723 os.remove(path)
713
724
714 path = self._ctx._repo.wjoin(self._path)
725 path = self._ctx._repo.wjoin(self._path)
715 shutil.rmtree(path, onerror=onerror)
726 shutil.rmtree(path, onerror=onerror)
716 try:
727 try:
717 os.removedirs(os.path.dirname(path))
728 os.removedirs(os.path.dirname(path))
718 except OSError:
729 except OSError:
719 pass
730 pass
720
731
721 def get(self, state, overwrite=False):
732 def get(self, state, overwrite=False):
722 if overwrite:
733 if overwrite:
723 self._svncommand(['revert', '--recursive'])
734 self._svncommand(['revert', '--recursive'])
724 args = ['checkout']
735 args = ['checkout']
725 if self._svnversion >= (1, 5):
736 if self._svnversion >= (1, 5):
726 args.append('--force')
737 args.append('--force')
727 # The revision must be specified at the end of the URL to properly
738 # The revision must be specified at the end of the URL to properly
728 # update to a directory which has since been deleted and recreated.
739 # update to a directory which has since been deleted and recreated.
729 args.append('%s@%s' % (state[0], state[1]))
740 args.append('%s@%s' % (state[0], state[1]))
730 status, err = self._svncommand(args, failok=True)
741 status, err = self._svncommand(args, failok=True)
731 if not re.search('Checked out revision [0-9]+.', status):
742 if not re.search('Checked out revision [0-9]+.', status):
732 if ('is already a working copy for a different URL' in err
743 if ('is already a working copy for a different URL' in err
733 and (self._wcchanged() == (False, False))):
744 and (self._wcchanged() == (False, False))):
734 # obstructed but clean working copy, so just blow it away.
745 # obstructed but clean working copy, so just blow it away.
735 self.remove()
746 self.remove()
736 self.get(state, overwrite=False)
747 self.get(state, overwrite=False)
737 return
748 return
738 raise util.Abort((status or err).splitlines()[-1])
749 raise util.Abort((status or err).splitlines()[-1])
739 self._ui.status(status)
750 self._ui.status(status)
740
751
741 def merge(self, state):
752 def merge(self, state):
742 old = self._state[1]
753 old = self._state[1]
743 new = state[1]
754 new = state[1]
744 if new != self._wcrev():
755 if new != self._wcrev():
745 dirty = old == self._wcrev() or self._wcchanged()[0]
756 dirty = old == self._wcrev() or self._wcchanged()[0]
746 if _updateprompt(self._ui, self, dirty, self._wcrev(), new):
757 if _updateprompt(self._ui, self, dirty, self._wcrev(), new):
747 self.get(state, False)
758 self.get(state, False)
748
759
749 def push(self, opts):
760 def push(self, opts):
750 # push is a no-op for SVN
761 # push is a no-op for SVN
751 return True
762 return True
752
763
753 def files(self):
764 def files(self):
754 output = self._svncommand(['list'])
765 output = self._svncommand(['list'])
755 # This works because svn forbids \n in filenames.
766 # This works because svn forbids \n in filenames.
756 return output.splitlines()
767 return output.splitlines()
757
768
758 def filedata(self, name):
769 def filedata(self, name):
759 return self._svncommand(['cat'], name)
770 return self._svncommand(['cat'], name)
760
771
761
772
762 class gitsubrepo(abstractsubrepo):
773 class gitsubrepo(abstractsubrepo):
763 def __init__(self, ctx, path, state):
774 def __init__(self, ctx, path, state):
764 # TODO add git version check.
775 # TODO add git version check.
765 self._state = state
776 self._state = state
766 self._ctx = ctx
777 self._ctx = ctx
767 self._path = path
778 self._path = path
768 self._relpath = os.path.join(reporelpath(ctx._repo), path)
779 self._relpath = os.path.join(reporelpath(ctx._repo), path)
769 self._abspath = ctx._repo.wjoin(path)
780 self._abspath = ctx._repo.wjoin(path)
770 self._subparent = ctx._repo
781 self._subparent = ctx._repo
771 self._ui = ctx._repo.ui
782 self._ui = ctx._repo.ui
772
783
773 def _gitcommand(self, commands, env=None, stream=False):
784 def _gitcommand(self, commands, env=None, stream=False):
774 return self._gitdir(commands, env=env, stream=stream)[0]
785 return self._gitdir(commands, env=env, stream=stream)[0]
775
786
776 def _gitdir(self, commands, env=None, stream=False):
787 def _gitdir(self, commands, env=None, stream=False):
777 return self._gitnodir(commands, env=env, stream=stream,
788 return self._gitnodir(commands, env=env, stream=stream,
778 cwd=self._abspath)
789 cwd=self._abspath)
779
790
780 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
791 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
781 """Calls the git command
792 """Calls the git command
782
793
783 The methods tries to call the git command. versions previor to 1.6.0
794 The methods tries to call the git command. versions previor to 1.6.0
784 are not supported and very probably fail.
795 are not supported and very probably fail.
785 """
796 """
786 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
797 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
787 # unless ui.quiet is set, print git's stderr,
798 # unless ui.quiet is set, print git's stderr,
788 # which is mostly progress and useful info
799 # which is mostly progress and useful info
789 errpipe = None
800 errpipe = None
790 if self._ui.quiet:
801 if self._ui.quiet:
791 errpipe = open(os.devnull, 'w')
802 errpipe = open(os.devnull, 'w')
792 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
803 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
793 close_fds=util.closefds,
804 close_fds=util.closefds,
794 stdout=subprocess.PIPE, stderr=errpipe)
805 stdout=subprocess.PIPE, stderr=errpipe)
795 if stream:
806 if stream:
796 return p.stdout, None
807 return p.stdout, None
797
808
798 retdata = p.stdout.read().strip()
809 retdata = p.stdout.read().strip()
799 # wait for the child to exit to avoid race condition.
810 # wait for the child to exit to avoid race condition.
800 p.wait()
811 p.wait()
801
812
802 if p.returncode != 0 and p.returncode != 1:
813 if p.returncode != 0 and p.returncode != 1:
803 # there are certain error codes that are ok
814 # there are certain error codes that are ok
804 command = commands[0]
815 command = commands[0]
805 if command in ('cat-file', 'symbolic-ref'):
816 if command in ('cat-file', 'symbolic-ref'):
806 return retdata, p.returncode
817 return retdata, p.returncode
807 # for all others, abort
818 # for all others, abort
808 raise util.Abort('git %s error %d in %s' %
819 raise util.Abort('git %s error %d in %s' %
809 (command, p.returncode, self._relpath))
820 (command, p.returncode, self._relpath))
810
821
811 return retdata, p.returncode
822 return retdata, p.returncode
812
823
813 def _gitmissing(self):
824 def _gitmissing(self):
814 return not os.path.exists(os.path.join(self._abspath, '.git'))
825 return not os.path.exists(os.path.join(self._abspath, '.git'))
815
826
816 def _gitstate(self):
827 def _gitstate(self):
817 return self._gitcommand(['rev-parse', 'HEAD'])
828 return self._gitcommand(['rev-parse', 'HEAD'])
818
829
819 def _gitcurrentbranch(self):
830 def _gitcurrentbranch(self):
820 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
831 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
821 if err:
832 if err:
822 current = None
833 current = None
823 return current
834 return current
824
835
825 def _gitremote(self, remote):
836 def _gitremote(self, remote):
826 out = self._gitcommand(['remote', 'show', '-n', remote])
837 out = self._gitcommand(['remote', 'show', '-n', remote])
827 line = out.split('\n')[1]
838 line = out.split('\n')[1]
828 i = line.index('URL: ') + len('URL: ')
839 i = line.index('URL: ') + len('URL: ')
829 return line[i:]
840 return line[i:]
830
841
831 def _githavelocally(self, revision):
842 def _githavelocally(self, revision):
832 out, code = self._gitdir(['cat-file', '-e', revision])
843 out, code = self._gitdir(['cat-file', '-e', revision])
833 return code == 0
844 return code == 0
834
845
835 def _gitisancestor(self, r1, r2):
846 def _gitisancestor(self, r1, r2):
836 base = self._gitcommand(['merge-base', r1, r2])
847 base = self._gitcommand(['merge-base', r1, r2])
837 return base == r1
848 return base == r1
838
849
839 def _gitisbare(self):
850 def _gitisbare(self):
840 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
851 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
841
852
842 def _gitupdatestat(self):
853 def _gitupdatestat(self):
843 """This must be run before git diff-index.
854 """This must be run before git diff-index.
844 diff-index only looks at changes to file stat;
855 diff-index only looks at changes to file stat;
845 this command looks at file contents and updates the stat."""
856 this command looks at file contents and updates the stat."""
846 self._gitcommand(['update-index', '-q', '--refresh'])
857 self._gitcommand(['update-index', '-q', '--refresh'])
847
858
848 def _gitbranchmap(self):
859 def _gitbranchmap(self):
849 '''returns 2 things:
860 '''returns 2 things:
850 a map from git branch to revision
861 a map from git branch to revision
851 a map from revision to branches'''
862 a map from revision to branches'''
852 branch2rev = {}
863 branch2rev = {}
853 rev2branch = {}
864 rev2branch = {}
854
865
855 out = self._gitcommand(['for-each-ref', '--format',
866 out = self._gitcommand(['for-each-ref', '--format',
856 '%(objectname) %(refname)'])
867 '%(objectname) %(refname)'])
857 for line in out.split('\n'):
868 for line in out.split('\n'):
858 revision, ref = line.split(' ')
869 revision, ref = line.split(' ')
859 if (not ref.startswith('refs/heads/') and
870 if (not ref.startswith('refs/heads/') and
860 not ref.startswith('refs/remotes/')):
871 not ref.startswith('refs/remotes/')):
861 continue
872 continue
862 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
873 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
863 continue # ignore remote/HEAD redirects
874 continue # ignore remote/HEAD redirects
864 branch2rev[ref] = revision
875 branch2rev[ref] = revision
865 rev2branch.setdefault(revision, []).append(ref)
876 rev2branch.setdefault(revision, []).append(ref)
866 return branch2rev, rev2branch
877 return branch2rev, rev2branch
867
878
868 def _gittracking(self, branches):
879 def _gittracking(self, branches):
869 'return map of remote branch to local tracking branch'
880 'return map of remote branch to local tracking branch'
870 # assumes no more than one local tracking branch for each remote
881 # assumes no more than one local tracking branch for each remote
871 tracking = {}
882 tracking = {}
872 for b in branches:
883 for b in branches:
873 if b.startswith('refs/remotes/'):
884 if b.startswith('refs/remotes/'):
874 continue
885 continue
875 bname = b.split('/', 2)[2]
886 bname = b.split('/', 2)[2]
876 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
887 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
877 if remote:
888 if remote:
878 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
889 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
879 tracking['refs/remotes/%s/%s' %
890 tracking['refs/remotes/%s/%s' %
880 (remote, ref.split('/', 2)[2])] = b
891 (remote, ref.split('/', 2)[2])] = b
881 return tracking
892 return tracking
882
893
883 def _abssource(self, source):
894 def _abssource(self, source):
884 if '://' not in source:
895 if '://' not in source:
885 # recognize the scp syntax as an absolute source
896 # recognize the scp syntax as an absolute source
886 colon = source.find(':')
897 colon = source.find(':')
887 if colon != -1 and '/' not in source[:colon]:
898 if colon != -1 and '/' not in source[:colon]:
888 return source
899 return source
889 self._subsource = source
900 self._subsource = source
890 return _abssource(self)
901 return _abssource(self)
891
902
892 def _fetch(self, source, revision):
903 def _fetch(self, source, revision):
893 if self._gitmissing():
904 if self._gitmissing():
894 source = self._abssource(source)
905 source = self._abssource(source)
895 self._ui.status(_('cloning subrepo %s from %s\n') %
906 self._ui.status(_('cloning subrepo %s from %s\n') %
896 (self._relpath, source))
907 (self._relpath, source))
897 self._gitnodir(['clone', source, self._abspath])
908 self._gitnodir(['clone', source, self._abspath])
898 if self._githavelocally(revision):
909 if self._githavelocally(revision):
899 return
910 return
900 self._ui.status(_('pulling subrepo %s from %s\n') %
911 self._ui.status(_('pulling subrepo %s from %s\n') %
901 (self._relpath, self._gitremote('origin')))
912 (self._relpath, self._gitremote('origin')))
902 # try only origin: the originally cloned repo
913 # try only origin: the originally cloned repo
903 self._gitcommand(['fetch'])
914 self._gitcommand(['fetch'])
904 if not self._githavelocally(revision):
915 if not self._githavelocally(revision):
905 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
916 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
906 (revision, self._relpath))
917 (revision, self._relpath))
907
918
908 def dirty(self, ignoreupdate=False):
919 def dirty(self, ignoreupdate=False):
909 if self._gitmissing():
920 if self._gitmissing():
910 return self._state[1] != ''
921 return self._state[1] != ''
911 if self._gitisbare():
922 if self._gitisbare():
912 return True
923 return True
913 if not ignoreupdate and self._state[1] != self._gitstate():
924 if not ignoreupdate and self._state[1] != self._gitstate():
914 # different version checked out
925 # different version checked out
915 return True
926 return True
916 # check for staged changes or modified files; ignore untracked files
927 # check for staged changes or modified files; ignore untracked files
917 self._gitupdatestat()
928 self._gitupdatestat()
918 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
929 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
919 return code == 1
930 return code == 1
920
931
921 def basestate(self):
932 def basestate(self):
922 return self._gitstate()
933 return self._gitstate()
923
934
924 def get(self, state, overwrite=False):
935 def get(self, state, overwrite=False):
925 source, revision, kind = state
936 source, revision, kind = state
926 if not revision:
937 if not revision:
927 self.remove()
938 self.remove()
928 return
939 return
929 self._fetch(source, revision)
940 self._fetch(source, revision)
930 # if the repo was set to be bare, unbare it
941 # if the repo was set to be bare, unbare it
931 if self._gitisbare():
942 if self._gitisbare():
932 self._gitcommand(['config', 'core.bare', 'false'])
943 self._gitcommand(['config', 'core.bare', 'false'])
933 if self._gitstate() == revision:
944 if self._gitstate() == revision:
934 self._gitcommand(['reset', '--hard', 'HEAD'])
945 self._gitcommand(['reset', '--hard', 'HEAD'])
935 return
946 return
936 elif self._gitstate() == revision:
947 elif self._gitstate() == revision:
937 if overwrite:
948 if overwrite:
938 # first reset the index to unmark new files for commit, because
949 # first reset the index to unmark new files for commit, because
939 # reset --hard will otherwise throw away files added for commit,
950 # reset --hard will otherwise throw away files added for commit,
940 # not just unmark them.
951 # not just unmark them.
941 self._gitcommand(['reset', 'HEAD'])
952 self._gitcommand(['reset', 'HEAD'])
942 self._gitcommand(['reset', '--hard', 'HEAD'])
953 self._gitcommand(['reset', '--hard', 'HEAD'])
943 return
954 return
944 branch2rev, rev2branch = self._gitbranchmap()
955 branch2rev, rev2branch = self._gitbranchmap()
945
956
946 def checkout(args):
957 def checkout(args):
947 cmd = ['checkout']
958 cmd = ['checkout']
948 if overwrite:
959 if overwrite:
949 # first reset the index to unmark new files for commit, because
960 # first reset the index to unmark new files for commit, because
950 # the -f option will otherwise throw away files added for
961 # the -f option will otherwise throw away files added for
951 # commit, not just unmark them.
962 # commit, not just unmark them.
952 self._gitcommand(['reset', 'HEAD'])
963 self._gitcommand(['reset', 'HEAD'])
953 cmd.append('-f')
964 cmd.append('-f')
954 self._gitcommand(cmd + args)
965 self._gitcommand(cmd + args)
955
966
956 def rawcheckout():
967 def rawcheckout():
957 # no branch to checkout, check it out with no branch
968 # no branch to checkout, check it out with no branch
958 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
969 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
959 self._relpath)
970 self._relpath)
960 self._ui.warn(_('check out a git branch if you intend '
971 self._ui.warn(_('check out a git branch if you intend '
961 'to make changes\n'))
972 'to make changes\n'))
962 checkout(['-q', revision])
973 checkout(['-q', revision])
963
974
964 if revision not in rev2branch:
975 if revision not in rev2branch:
965 rawcheckout()
976 rawcheckout()
966 return
977 return
967 branches = rev2branch[revision]
978 branches = rev2branch[revision]
968 firstlocalbranch = None
979 firstlocalbranch = None
969 for b in branches:
980 for b in branches:
970 if b == 'refs/heads/master':
981 if b == 'refs/heads/master':
971 # master trumps all other branches
982 # master trumps all other branches
972 checkout(['refs/heads/master'])
983 checkout(['refs/heads/master'])
973 return
984 return
974 if not firstlocalbranch and not b.startswith('refs/remotes/'):
985 if not firstlocalbranch and not b.startswith('refs/remotes/'):
975 firstlocalbranch = b
986 firstlocalbranch = b
976 if firstlocalbranch:
987 if firstlocalbranch:
977 checkout([firstlocalbranch])
988 checkout([firstlocalbranch])
978 return
989 return
979
990
980 tracking = self._gittracking(branch2rev.keys())
991 tracking = self._gittracking(branch2rev.keys())
981 # choose a remote branch already tracked if possible
992 # choose a remote branch already tracked if possible
982 remote = branches[0]
993 remote = branches[0]
983 if remote not in tracking:
994 if remote not in tracking:
984 for b in branches:
995 for b in branches:
985 if b in tracking:
996 if b in tracking:
986 remote = b
997 remote = b
987 break
998 break
988
999
989 if remote not in tracking:
1000 if remote not in tracking:
990 # create a new local tracking branch
1001 # create a new local tracking branch
991 local = remote.split('/', 2)[2]
1002 local = remote.split('/', 2)[2]
992 checkout(['-b', local, remote])
1003 checkout(['-b', local, remote])
993 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1004 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
994 # When updating to a tracked remote branch,
1005 # When updating to a tracked remote branch,
995 # if the local tracking branch is downstream of it,
1006 # if the local tracking branch is downstream of it,
996 # a normal `git pull` would have performed a "fast-forward merge"
1007 # a normal `git pull` would have performed a "fast-forward merge"
997 # which is equivalent to updating the local branch to the remote.
1008 # which is equivalent to updating the local branch to the remote.
998 # Since we are only looking at branching at update, we need to
1009 # Since we are only looking at branching at update, we need to
999 # detect this situation and perform this action lazily.
1010 # detect this situation and perform this action lazily.
1000 if tracking[remote] != self._gitcurrentbranch():
1011 if tracking[remote] != self._gitcurrentbranch():
1001 checkout([tracking[remote]])
1012 checkout([tracking[remote]])
1002 self._gitcommand(['merge', '--ff', remote])
1013 self._gitcommand(['merge', '--ff', remote])
1003 else:
1014 else:
1004 # a real merge would be required, just checkout the revision
1015 # a real merge would be required, just checkout the revision
1005 rawcheckout()
1016 rawcheckout()
1006
1017
1007 def commit(self, text, user, date):
1018 def commit(self, text, user, date):
1008 if self._gitmissing():
1019 if self._gitmissing():
1009 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1020 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1010 cmd = ['commit', '-a', '-m', text]
1021 cmd = ['commit', '-a', '-m', text]
1011 env = os.environ.copy()
1022 env = os.environ.copy()
1012 if user:
1023 if user:
1013 cmd += ['--author', user]
1024 cmd += ['--author', user]
1014 if date:
1025 if date:
1015 # git's date parser silently ignores when seconds < 1e9
1026 # git's date parser silently ignores when seconds < 1e9
1016 # convert to ISO8601
1027 # convert to ISO8601
1017 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1028 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1018 '%Y-%m-%dT%H:%M:%S %1%2')
1029 '%Y-%m-%dT%H:%M:%S %1%2')
1019 self._gitcommand(cmd, env=env)
1030 self._gitcommand(cmd, env=env)
1020 # make sure commit works otherwise HEAD might not exist under certain
1031 # make sure commit works otherwise HEAD might not exist under certain
1021 # circumstances
1032 # circumstances
1022 return self._gitstate()
1033 return self._gitstate()
1023
1034
1024 def merge(self, state):
1035 def merge(self, state):
1025 source, revision, kind = state
1036 source, revision, kind = state
1026 self._fetch(source, revision)
1037 self._fetch(source, revision)
1027 base = self._gitcommand(['merge-base', revision, self._state[1]])
1038 base = self._gitcommand(['merge-base', revision, self._state[1]])
1028 self._gitupdatestat()
1039 self._gitupdatestat()
1029 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1040 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1030
1041
1031 def mergefunc():
1042 def mergefunc():
1032 if base == revision:
1043 if base == revision:
1033 self.get(state) # fast forward merge
1044 self.get(state) # fast forward merge
1034 elif base != self._state[1]:
1045 elif base != self._state[1]:
1035 self._gitcommand(['merge', '--no-commit', revision])
1046 self._gitcommand(['merge', '--no-commit', revision])
1036
1047
1037 if self.dirty():
1048 if self.dirty():
1038 if self._gitstate() != revision:
1049 if self._gitstate() != revision:
1039 dirty = self._gitstate() == self._state[1] or code != 0
1050 dirty = self._gitstate() == self._state[1] or code != 0
1040 if _updateprompt(self._ui, self, dirty,
1051 if _updateprompt(self._ui, self, dirty,
1041 self._state[1][:7], revision[:7]):
1052 self._state[1][:7], revision[:7]):
1042 mergefunc()
1053 mergefunc()
1043 else:
1054 else:
1044 mergefunc()
1055 mergefunc()
1045
1056
1046 def push(self, opts):
1057 def push(self, opts):
1047 force = opts.get('force')
1058 force = opts.get('force')
1048
1059
1049 if not self._state[1]:
1060 if not self._state[1]:
1050 return True
1061 return True
1051 if self._gitmissing():
1062 if self._gitmissing():
1052 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1063 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1053 # if a branch in origin contains the revision, nothing to do
1064 # if a branch in origin contains the revision, nothing to do
1054 branch2rev, rev2branch = self._gitbranchmap()
1065 branch2rev, rev2branch = self._gitbranchmap()
1055 if self._state[1] in rev2branch:
1066 if self._state[1] in rev2branch:
1056 for b in rev2branch[self._state[1]]:
1067 for b in rev2branch[self._state[1]]:
1057 if b.startswith('refs/remotes/origin/'):
1068 if b.startswith('refs/remotes/origin/'):
1058 return True
1069 return True
1059 for b, revision in branch2rev.iteritems():
1070 for b, revision in branch2rev.iteritems():
1060 if b.startswith('refs/remotes/origin/'):
1071 if b.startswith('refs/remotes/origin/'):
1061 if self._gitisancestor(self._state[1], revision):
1072 if self._gitisancestor(self._state[1], revision):
1062 return True
1073 return True
1063 # otherwise, try to push the currently checked out branch
1074 # otherwise, try to push the currently checked out branch
1064 cmd = ['push']
1075 cmd = ['push']
1065 if force:
1076 if force:
1066 cmd.append('--force')
1077 cmd.append('--force')
1067
1078
1068 current = self._gitcurrentbranch()
1079 current = self._gitcurrentbranch()
1069 if current:
1080 if current:
1070 # determine if the current branch is even useful
1081 # determine if the current branch is even useful
1071 if not self._gitisancestor(self._state[1], current):
1082 if not self._gitisancestor(self._state[1], current):
1072 self._ui.warn(_('unrelated git branch checked out '
1083 self._ui.warn(_('unrelated git branch checked out '
1073 'in subrepo %s\n') % self._relpath)
1084 'in subrepo %s\n') % self._relpath)
1074 return False
1085 return False
1075 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1086 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1076 (current.split('/', 2)[2], self._relpath))
1087 (current.split('/', 2)[2], self._relpath))
1077 self._gitcommand(cmd + ['origin', current])
1088 self._gitcommand(cmd + ['origin', current])
1078 return True
1089 return True
1079 else:
1090 else:
1080 self._ui.warn(_('no branch checked out in subrepo %s\n'
1091 self._ui.warn(_('no branch checked out in subrepo %s\n'
1081 'cannot push revision %s') %
1092 'cannot push revision %s') %
1082 (self._relpath, self._state[1]))
1093 (self._relpath, self._state[1]))
1083 return False
1094 return False
1084
1095
1085 def remove(self):
1096 def remove(self):
1086 if self._gitmissing():
1097 if self._gitmissing():
1087 return
1098 return
1088 if self.dirty():
1099 if self.dirty():
1089 self._ui.warn(_('not removing repo %s because '
1100 self._ui.warn(_('not removing repo %s because '
1090 'it has changes.\n') % self._relpath)
1101 'it has changes.\n') % self._relpath)
1091 return
1102 return
1092 # we can't fully delete the repository as it may contain
1103 # we can't fully delete the repository as it may contain
1093 # local-only history
1104 # local-only history
1094 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1105 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1095 self._gitcommand(['config', 'core.bare', 'true'])
1106 self._gitcommand(['config', 'core.bare', 'true'])
1096 for f in os.listdir(self._abspath):
1107 for f in os.listdir(self._abspath):
1097 if f == '.git':
1108 if f == '.git':
1098 continue
1109 continue
1099 path = os.path.join(self._abspath, f)
1110 path = os.path.join(self._abspath, f)
1100 if os.path.isdir(path) and not os.path.islink(path):
1111 if os.path.isdir(path) and not os.path.islink(path):
1101 shutil.rmtree(path)
1112 shutil.rmtree(path)
1102 else:
1113 else:
1103 os.remove(path)
1114 os.remove(path)
1104
1115
1105 def archive(self, ui, archiver, prefix):
1116 def archive(self, ui, archiver, prefix):
1106 source, revision = self._state
1117 source, revision = self._state
1107 if not revision:
1118 if not revision:
1108 return
1119 return
1109 self._fetch(source, revision)
1120 self._fetch(source, revision)
1110
1121
1111 # Parse git's native archive command.
1122 # Parse git's native archive command.
1112 # This should be much faster than manually traversing the trees
1123 # This should be much faster than manually traversing the trees
1113 # and objects with many subprocess calls.
1124 # and objects with many subprocess calls.
1114 tarstream = self._gitcommand(['archive', revision], stream=True)
1125 tarstream = self._gitcommand(['archive', revision], stream=True)
1115 tar = tarfile.open(fileobj=tarstream, mode='r|')
1126 tar = tarfile.open(fileobj=tarstream, mode='r|')
1116 relpath = subrelpath(self)
1127 relpath = subrelpath(self)
1117 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1128 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1118 for i, info in enumerate(tar):
1129 for i, info in enumerate(tar):
1119 if info.isdir():
1130 if info.isdir():
1120 continue
1131 continue
1121 if info.issym():
1132 if info.issym():
1122 data = info.linkname
1133 data = info.linkname
1123 else:
1134 else:
1124 data = tar.extractfile(info).read()
1135 data = tar.extractfile(info).read()
1125 archiver.addfile(os.path.join(prefix, self._path, info.name),
1136 archiver.addfile(os.path.join(prefix, self._path, info.name),
1126 info.mode, info.issym(), data)
1137 info.mode, info.issym(), data)
1127 ui.progress(_('archiving (%s)') % relpath, i + 1,
1138 ui.progress(_('archiving (%s)') % relpath, i + 1,
1128 unit=_('files'))
1139 unit=_('files'))
1129 ui.progress(_('archiving (%s)') % relpath, None)
1140 ui.progress(_('archiving (%s)') % relpath, None)
1130
1141
1131
1142
1132 def status(self, rev2, **opts):
1143 def status(self, rev2, **opts):
1133 rev1 = self._state[1]
1144 rev1 = self._state[1]
1134 if self._gitmissing() or not rev1:
1145 if self._gitmissing() or not rev1:
1135 # if the repo is missing, return no results
1146 # if the repo is missing, return no results
1136 return [], [], [], [], [], [], []
1147 return [], [], [], [], [], [], []
1137 modified, added, removed = [], [], []
1148 modified, added, removed = [], [], []
1138 self._gitupdatestat()
1149 self._gitupdatestat()
1139 if rev2:
1150 if rev2:
1140 command = ['diff-tree', rev1, rev2]
1151 command = ['diff-tree', rev1, rev2]
1141 else:
1152 else:
1142 command = ['diff-index', rev1]
1153 command = ['diff-index', rev1]
1143 out = self._gitcommand(command)
1154 out = self._gitcommand(command)
1144 for line in out.split('\n'):
1155 for line in out.split('\n'):
1145 tab = line.find('\t')
1156 tab = line.find('\t')
1146 if tab == -1:
1157 if tab == -1:
1147 continue
1158 continue
1148 status, f = line[tab - 1], line[tab + 1:]
1159 status, f = line[tab - 1], line[tab + 1:]
1149 if status == 'M':
1160 if status == 'M':
1150 modified.append(f)
1161 modified.append(f)
1151 elif status == 'A':
1162 elif status == 'A':
1152 added.append(f)
1163 added.append(f)
1153 elif status == 'D':
1164 elif status == 'D':
1154 removed.append(f)
1165 removed.append(f)
1155
1166
1156 deleted = unknown = ignored = clean = []
1167 deleted = unknown = ignored = clean = []
1157 return modified, added, removed, deleted, unknown, ignored, clean
1168 return modified, added, removed, deleted, unknown, ignored, clean
1158
1169
1159 types = {
1170 types = {
1160 'hg': hgsubrepo,
1171 'hg': hgsubrepo,
1161 'svn': svnsubrepo,
1172 'svn': svnsubrepo,
1162 'git': gitsubrepo,
1173 'git': gitsubrepo,
1163 }
1174 }
@@ -1,1016 +1,1019
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 $ rm -rf sub
6 $ rm -rf sub
7 $ mkdir sub
7 $ mkdir sub
8 $ cd sub
8 $ cd sub
9 $ hg init t
9 $ hg init t
10 $ cd t
10 $ cd t
11
11
12 first revision, no sub
12 first revision, no sub
13
13
14 $ echo a > a
14 $ echo a > a
15 $ hg ci -Am0
15 $ hg ci -Am0
16 adding a
16 adding a
17
17
18 add first sub
18 add first sub
19
19
20 $ echo s = s > .hgsub
20 $ echo s = s > .hgsub
21 $ hg add .hgsub
21 $ hg add .hgsub
22 $ hg init s
22 $ hg init s
23 $ echo a > s/a
23 $ echo a > s/a
24
24
25 Issue2232: committing a subrepo without .hgsub
25 Issue2232: committing a subrepo without .hgsub
26
26
27 $ hg ci -mbad s
27 $ hg ci -mbad s
28 abort: can't commit subrepos without .hgsub
28 abort: can't commit subrepos without .hgsub
29 [255]
29 [255]
30
30
31 $ hg -R s ci -Ams0
31 $ hg -R s ci -Ams0
32 adding a
32 adding a
33 $ hg sum
33 $ hg sum
34 parent: 0:f7b1eb17ad24 tip
34 parent: 0:f7b1eb17ad24 tip
35 0
35 0
36 branch: default
36 branch: default
37 commit: 1 added, 1 subrepos
37 commit: 1 added, 1 subrepos
38 update: (current)
38 update: (current)
39 $ hg ci -m1
39 $ hg ci -m1
40
40
41 Revert can't (yet) revert subrepos:
41 Revert can't (yet) revert subrepos:
42
42
43 $ echo b > s/a
43 $ echo b > s/a
44 $ hg revert s
44 $ hg revert s
45 s: reverting subrepos is unsupported
45 abort: cannot revert subrepos without --no-backup
46 [255]
46
47
47 Revert currently ignores subrepos by default
48 Revert currently ignores subrepos by default
48
49
49 $ hg revert -a
50 $ hg revert -a
51 abort: cannot revert subrepos without --no-backup
52 [255]
50 $ hg revert -R s -a -C
53 $ hg revert -R s -a -C
51 reverting s/a (glob)
54 reverting s/a (glob)
52
55
53 Issue2022: update -C
56 Issue2022: update -C
54
57
55 $ echo b > s/a
58 $ echo b > s/a
56 $ hg sum
59 $ hg sum
57 parent: 1:7cf8cfea66e4 tip
60 parent: 1:7cf8cfea66e4 tip
58 1
61 1
59 branch: default
62 branch: default
60 commit: 1 subrepos
63 commit: 1 subrepos
61 update: (current)
64 update: (current)
62 $ hg co -C 1
65 $ hg co -C 1
63 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 $ hg sum
67 $ hg sum
65 parent: 1:7cf8cfea66e4 tip
68 parent: 1:7cf8cfea66e4 tip
66 1
69 1
67 branch: default
70 branch: default
68 commit: (clean)
71 commit: (clean)
69 update: (current)
72 update: (current)
70
73
71 commands that require a clean repo should respect subrepos
74 commands that require a clean repo should respect subrepos
72
75
73 $ echo b >> s/a
76 $ echo b >> s/a
74 $ hg backout tip
77 $ hg backout tip
75 abort: uncommitted changes in subrepo s
78 abort: uncommitted changes in subrepo s
76 [255]
79 [255]
77 $ hg revert -C -R s s/a
80 $ hg revert -C -R s s/a
78
81
79 add sub sub
82 add sub sub
80
83
81 $ echo ss = ss > s/.hgsub
84 $ echo ss = ss > s/.hgsub
82 $ hg init s/ss
85 $ hg init s/ss
83 $ echo a > s/ss/a
86 $ echo a > s/ss/a
84 $ hg -R s add s/.hgsub
87 $ hg -R s add s/.hgsub
85 $ hg -R s/ss add s/ss/a
88 $ hg -R s/ss add s/ss/a
86 $ hg sum
89 $ hg sum
87 parent: 1:7cf8cfea66e4 tip
90 parent: 1:7cf8cfea66e4 tip
88 1
91 1
89 branch: default
92 branch: default
90 commit: 1 subrepos
93 commit: 1 subrepos
91 update: (current)
94 update: (current)
92 $ hg ci -m2
95 $ hg ci -m2
93 committing subrepository s
96 committing subrepository s
94 committing subrepository s/ss (glob)
97 committing subrepository s/ss (glob)
95 $ hg sum
98 $ hg sum
96 parent: 2:df30734270ae tip
99 parent: 2:df30734270ae tip
97 2
100 2
98 branch: default
101 branch: default
99 commit: (clean)
102 commit: (clean)
100 update: (current)
103 update: (current)
101
104
102 bump sub rev (and check it is ignored by ui.commitsubrepos)
105 bump sub rev (and check it is ignored by ui.commitsubrepos)
103
106
104 $ echo b > s/a
107 $ echo b > s/a
105 $ hg -R s ci -ms1
108 $ hg -R s ci -ms1
106 $ hg --config ui.commitsubrepos=no ci -m3
109 $ hg --config ui.commitsubrepos=no ci -m3
107
110
108 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
111 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
109
112
110 $ echo c > s/a
113 $ echo c > s/a
111 $ hg --config ui.commitsubrepos=no ci -m4
114 $ hg --config ui.commitsubrepos=no ci -m4
112 abort: uncommitted changes in subrepo s
115 abort: uncommitted changes in subrepo s
113 (use --subrepos for recursive commit)
116 (use --subrepos for recursive commit)
114 [255]
117 [255]
115 $ hg ci -m4
118 $ hg ci -m4
116 committing subrepository s
119 committing subrepository s
117 $ hg tip -R s
120 $ hg tip -R s
118 changeset: 3:1c833a7a9e3a
121 changeset: 3:1c833a7a9e3a
119 tag: tip
122 tag: tip
120 user: test
123 user: test
121 date: Thu Jan 01 00:00:00 1970 +0000
124 date: Thu Jan 01 00:00:00 1970 +0000
122 summary: 4
125 summary: 4
123
126
124
127
125 check caching
128 check caching
126
129
127 $ hg co 0
130 $ hg co 0
128 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
131 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
129 $ hg debugsub
132 $ hg debugsub
130
133
131 restore
134 restore
132
135
133 $ hg co
136 $ hg co
134 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
137 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
135 $ hg debugsub
138 $ hg debugsub
136 path s
139 path s
137 source s
140 source s
138 revision 1c833a7a9e3a4445c711aaf0f012379cd0d4034e
141 revision 1c833a7a9e3a4445c711aaf0f012379cd0d4034e
139
142
140 new branch for merge tests
143 new branch for merge tests
141
144
142 $ hg co 1
145 $ hg co 1
143 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
146 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
144 $ echo t = t >> .hgsub
147 $ echo t = t >> .hgsub
145 $ hg init t
148 $ hg init t
146 $ echo t > t/t
149 $ echo t > t/t
147 $ hg -R t add t
150 $ hg -R t add t
148 adding t/t (glob)
151 adding t/t (glob)
149
152
150 5
153 5
151
154
152 $ hg ci -m5 # add sub
155 $ hg ci -m5 # add sub
153 committing subrepository t
156 committing subrepository t
154 created new head
157 created new head
155 $ echo t2 > t/t
158 $ echo t2 > t/t
156
159
157 6
160 6
158
161
159 $ hg st -R s
162 $ hg st -R s
160 $ hg ci -m6 # change sub
163 $ hg ci -m6 # change sub
161 committing subrepository t
164 committing subrepository t
162 $ hg debugsub
165 $ hg debugsub
163 path s
166 path s
164 source s
167 source s
165 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
168 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
166 path t
169 path t
167 source t
170 source t
168 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
171 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
169 $ echo t3 > t/t
172 $ echo t3 > t/t
170
173
171 7
174 7
172
175
173 $ hg ci -m7 # change sub again for conflict test
176 $ hg ci -m7 # change sub again for conflict test
174 committing subrepository t
177 committing subrepository t
175 $ hg rm .hgsub
178 $ hg rm .hgsub
176
179
177 8
180 8
178
181
179 $ hg ci -m8 # remove sub
182 $ hg ci -m8 # remove sub
180
183
181 merge tests
184 merge tests
182
185
183 $ hg co -C 3
186 $ hg co -C 3
184 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
185 $ hg merge 5 # test adding
188 $ hg merge 5 # test adding
186 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
189 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 (branch merge, don't forget to commit)
190 (branch merge, don't forget to commit)
188 $ hg debugsub
191 $ hg debugsub
189 path s
192 path s
190 source s
193 source s
191 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
194 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
192 path t
195 path t
193 source t
196 source t
194 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
197 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
195 $ hg ci -m9
198 $ hg ci -m9
196 created new head
199 created new head
197 $ hg merge 6 --debug # test change
200 $ hg merge 6 --debug # test change
198 searching for copies back to rev 2
201 searching for copies back to rev 2
199 resolving manifests
202 resolving manifests
200 overwrite: False, partial: False
203 overwrite: False, partial: False
201 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
204 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
202 .hgsubstate: versions differ -> m
205 .hgsubstate: versions differ -> m
203 updating: .hgsubstate 1/1 files (100.00%)
206 updating: .hgsubstate 1/1 files (100.00%)
204 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
207 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
205 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
208 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
206 getting subrepo t
209 getting subrepo t
207 resolving manifests
210 resolving manifests
208 overwrite: True, partial: False
211 overwrite: True, partial: False
209 ancestor: 60ca1237c194+, local: 60ca1237c194+, remote: 6747d179aa9a
212 ancestor: 60ca1237c194+, local: 60ca1237c194+, remote: 6747d179aa9a
210 t: remote is newer -> g
213 t: remote is newer -> g
211 updating: t 1/1 files (100.00%)
214 updating: t 1/1 files (100.00%)
212 getting t
215 getting t
213 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
216 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
214 (branch merge, don't forget to commit)
217 (branch merge, don't forget to commit)
215 $ hg debugsub
218 $ hg debugsub
216 path s
219 path s
217 source s
220 source s
218 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
221 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
219 path t
222 path t
220 source t
223 source t
221 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
224 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
222 $ echo conflict > t/t
225 $ echo conflict > t/t
223 $ hg ci -m10
226 $ hg ci -m10
224 committing subrepository t
227 committing subrepository t
225 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
228 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
226 searching for copies back to rev 2
229 searching for copies back to rev 2
227 resolving manifests
230 resolving manifests
228 overwrite: False, partial: False
231 overwrite: False, partial: False
229 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
232 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
230 .hgsubstate: versions differ -> m
233 .hgsubstate: versions differ -> m
231 updating: .hgsubstate 1/1 files (100.00%)
234 updating: .hgsubstate 1/1 files (100.00%)
232 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
235 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
233 subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
236 subrepo t: both sides changed, merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
234 merging subrepo t
237 merging subrepo t
235 searching for copies back to rev 2
238 searching for copies back to rev 2
236 resolving manifests
239 resolving manifests
237 overwrite: False, partial: False
240 overwrite: False, partial: False
238 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
241 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
239 t: versions differ -> m
242 t: versions differ -> m
240 preserving t for resolve of t
243 preserving t for resolve of t
241 updating: t 1/1 files (100.00%)
244 updating: t 1/1 files (100.00%)
242 picked tool 'internal:merge' for t (binary False symlink False)
245 picked tool 'internal:merge' for t (binary False symlink False)
243 merging t
246 merging t
244 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
247 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
245 warning: conflicts during merge.
248 warning: conflicts during merge.
246 merging t incomplete! (edit conflicts, then use 'hg resolve --mark')
249 merging t incomplete! (edit conflicts, then use 'hg resolve --mark')
247 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
250 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
248 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
251 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
249 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
252 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
250 (branch merge, don't forget to commit)
253 (branch merge, don't forget to commit)
251
254
252 should conflict
255 should conflict
253
256
254 $ cat t/t
257 $ cat t/t
255 <<<<<<< local
258 <<<<<<< local
256 conflict
259 conflict
257 =======
260 =======
258 t3
261 t3
259 >>>>>>> other
262 >>>>>>> other
260
263
261 clone
264 clone
262
265
263 $ cd ..
266 $ cd ..
264 $ hg clone t tc
267 $ hg clone t tc
265 updating to branch default
268 updating to branch default
266 cloning subrepo s from $TESTTMP/sub/t/s (glob)
269 cloning subrepo s from $TESTTMP/sub/t/s (glob)
267 cloning subrepo s/ss from $TESTTMP/sub/t/s/ss (glob)
270 cloning subrepo s/ss from $TESTTMP/sub/t/s/ss (glob)
268 cloning subrepo t from $TESTTMP/sub/t/t (glob)
271 cloning subrepo t from $TESTTMP/sub/t/t (glob)
269 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
272 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 $ cd tc
273 $ cd tc
271 $ hg debugsub
274 $ hg debugsub
272 path s
275 path s
273 source s
276 source s
274 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
277 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
275 path t
278 path t
276 source t
279 source t
277 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
280 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
278
281
279 push
282 push
280
283
281 $ echo bah > t/t
284 $ echo bah > t/t
282 $ hg ci -m11
285 $ hg ci -m11
283 committing subrepository t
286 committing subrepository t
284 $ hg push
287 $ hg push
285 pushing to $TESTTMP/sub/t (glob)
288 pushing to $TESTTMP/sub/t (glob)
286 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss (glob)
289 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss (glob)
287 searching for changes
290 searching for changes
288 no changes found
291 no changes found
289 pushing subrepo s to $TESTTMP/sub/t/s (glob)
292 pushing subrepo s to $TESTTMP/sub/t/s (glob)
290 searching for changes
293 searching for changes
291 no changes found
294 no changes found
292 pushing subrepo t to $TESTTMP/sub/t/t (glob)
295 pushing subrepo t to $TESTTMP/sub/t/t (glob)
293 searching for changes
296 searching for changes
294 adding changesets
297 adding changesets
295 adding manifests
298 adding manifests
296 adding file changes
299 adding file changes
297 added 1 changesets with 1 changes to 1 files
300 added 1 changesets with 1 changes to 1 files
298 searching for changes
301 searching for changes
299 adding changesets
302 adding changesets
300 adding manifests
303 adding manifests
301 adding file changes
304 adding file changes
302 added 1 changesets with 1 changes to 1 files
305 added 1 changesets with 1 changes to 1 files
303
306
304 push -f
307 push -f
305
308
306 $ echo bah > s/a
309 $ echo bah > s/a
307 $ hg ci -m12
310 $ hg ci -m12
308 committing subrepository s
311 committing subrepository s
309 $ hg push
312 $ hg push
310 pushing to $TESTTMP/sub/t (glob)
313 pushing to $TESTTMP/sub/t (glob)
311 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss (glob)
314 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss (glob)
312 searching for changes
315 searching for changes
313 no changes found
316 no changes found
314 pushing subrepo s to $TESTTMP/sub/t/s (glob)
317 pushing subrepo s to $TESTTMP/sub/t/s (glob)
315 searching for changes
318 searching for changes
316 abort: push creates new remote head 12a213df6fa9!
319 abort: push creates new remote head 12a213df6fa9!
317 (did you forget to merge? use push -f to force)
320 (did you forget to merge? use push -f to force)
318 [255]
321 [255]
319 $ hg push -f
322 $ hg push -f
320 pushing to $TESTTMP/sub/t (glob)
323 pushing to $TESTTMP/sub/t (glob)
321 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss (glob)
324 pushing subrepo s/ss to $TESTTMP/sub/t/s/ss (glob)
322 searching for changes
325 searching for changes
323 no changes found
326 no changes found
324 pushing subrepo s to $TESTTMP/sub/t/s (glob)
327 pushing subrepo s to $TESTTMP/sub/t/s (glob)
325 searching for changes
328 searching for changes
326 adding changesets
329 adding changesets
327 adding manifests
330 adding manifests
328 adding file changes
331 adding file changes
329 added 1 changesets with 1 changes to 1 files (+1 heads)
332 added 1 changesets with 1 changes to 1 files (+1 heads)
330 pushing subrepo t to $TESTTMP/sub/t/t (glob)
333 pushing subrepo t to $TESTTMP/sub/t/t (glob)
331 searching for changes
334 searching for changes
332 no changes found
335 no changes found
333 searching for changes
336 searching for changes
334 adding changesets
337 adding changesets
335 adding manifests
338 adding manifests
336 adding file changes
339 adding file changes
337 added 1 changesets with 1 changes to 1 files
340 added 1 changesets with 1 changes to 1 files
338
341
339 update
342 update
340
343
341 $ cd ../t
344 $ cd ../t
342 $ hg up -C # discard our earlier merge
345 $ hg up -C # discard our earlier merge
343 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
346 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
344 $ echo blah > t/t
347 $ echo blah > t/t
345 $ hg ci -m13
348 $ hg ci -m13
346 committing subrepository t
349 committing subrepository t
347
350
348 pull
351 pull
349
352
350 $ cd ../tc
353 $ cd ../tc
351 $ hg pull
354 $ hg pull
352 pulling from $TESTTMP/sub/t (glob)
355 pulling from $TESTTMP/sub/t (glob)
353 searching for changes
356 searching for changes
354 adding changesets
357 adding changesets
355 adding manifests
358 adding manifests
356 adding file changes
359 adding file changes
357 added 1 changesets with 1 changes to 1 files
360 added 1 changesets with 1 changes to 1 files
358 (run 'hg update' to get a working copy)
361 (run 'hg update' to get a working copy)
359
362
360 should pull t
363 should pull t
361
364
362 $ hg up
365 $ hg up
363 pulling subrepo t from $TESTTMP/sub/t/t (glob)
366 pulling subrepo t from $TESTTMP/sub/t/t (glob)
364 searching for changes
367 searching for changes
365 adding changesets
368 adding changesets
366 adding manifests
369 adding manifests
367 adding file changes
370 adding file changes
368 added 1 changesets with 1 changes to 1 files
371 added 1 changesets with 1 changes to 1 files
369 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
372 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
370 $ cat t/t
373 $ cat t/t
371 blah
374 blah
372
375
373 bogus subrepo path aborts
376 bogus subrepo path aborts
374
377
375 $ echo 'bogus=[boguspath' >> .hgsub
378 $ echo 'bogus=[boguspath' >> .hgsub
376 $ hg ci -m 'bogus subrepo path'
379 $ hg ci -m 'bogus subrepo path'
377 abort: missing ] in subrepo source
380 abort: missing ] in subrepo source
378 [255]
381 [255]
379
382
380 Issue1986: merge aborts when trying to merge a subrepo that
383 Issue1986: merge aborts when trying to merge a subrepo that
381 shouldn't need merging
384 shouldn't need merging
382
385
383 # subrepo layout
386 # subrepo layout
384 #
387 #
385 # o 5 br
388 # o 5 br
386 # /|
389 # /|
387 # o | 4 default
390 # o | 4 default
388 # | |
391 # | |
389 # | o 3 br
392 # | o 3 br
390 # |/|
393 # |/|
391 # o | 2 default
394 # o | 2 default
392 # | |
395 # | |
393 # | o 1 br
396 # | o 1 br
394 # |/
397 # |/
395 # o 0 default
398 # o 0 default
396
399
397 $ cd ..
400 $ cd ..
398 $ rm -rf sub
401 $ rm -rf sub
399 $ hg init main
402 $ hg init main
400 $ cd main
403 $ cd main
401 $ hg init s
404 $ hg init s
402 $ cd s
405 $ cd s
403 $ echo a > a
406 $ echo a > a
404 $ hg ci -Am1
407 $ hg ci -Am1
405 adding a
408 adding a
406 $ hg branch br
409 $ hg branch br
407 marked working directory as branch br
410 marked working directory as branch br
408 (branches are permanent and global, did you want a bookmark?)
411 (branches are permanent and global, did you want a bookmark?)
409 $ echo a >> a
412 $ echo a >> a
410 $ hg ci -m1
413 $ hg ci -m1
411 $ hg up default
414 $ hg up default
412 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
415 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
413 $ echo b > b
416 $ echo b > b
414 $ hg ci -Am1
417 $ hg ci -Am1
415 adding b
418 adding b
416 $ hg up br
419 $ hg up br
417 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
420 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
418 $ hg merge tip
421 $ hg merge tip
419 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
422 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
420 (branch merge, don't forget to commit)
423 (branch merge, don't forget to commit)
421 $ hg ci -m1
424 $ hg ci -m1
422 $ hg up 2
425 $ hg up 2
423 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
426 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
424 $ echo c > c
427 $ echo c > c
425 $ hg ci -Am1
428 $ hg ci -Am1
426 adding c
429 adding c
427 $ hg up 3
430 $ hg up 3
428 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
431 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
429 $ hg merge 4
432 $ hg merge 4
430 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
433 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
431 (branch merge, don't forget to commit)
434 (branch merge, don't forget to commit)
432 $ hg ci -m1
435 $ hg ci -m1
433
436
434 # main repo layout:
437 # main repo layout:
435 #
438 #
436 # * <-- try to merge default into br again
439 # * <-- try to merge default into br again
437 # .`|
440 # .`|
438 # . o 5 br --> substate = 5
441 # . o 5 br --> substate = 5
439 # . |
442 # . |
440 # o | 4 default --> substate = 4
443 # o | 4 default --> substate = 4
441 # | |
444 # | |
442 # | o 3 br --> substate = 2
445 # | o 3 br --> substate = 2
443 # |/|
446 # |/|
444 # o | 2 default --> substate = 2
447 # o | 2 default --> substate = 2
445 # | |
448 # | |
446 # | o 1 br --> substate = 3
449 # | o 1 br --> substate = 3
447 # |/
450 # |/
448 # o 0 default --> substate = 2
451 # o 0 default --> substate = 2
449
452
450 $ cd ..
453 $ cd ..
451 $ echo 's = s' > .hgsub
454 $ echo 's = s' > .hgsub
452 $ hg -R s up 2
455 $ hg -R s up 2
453 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
456 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
454 $ hg ci -Am1
457 $ hg ci -Am1
455 adding .hgsub
458 adding .hgsub
456 $ hg branch br
459 $ hg branch br
457 marked working directory as branch br
460 marked working directory as branch br
458 (branches are permanent and global, did you want a bookmark?)
461 (branches are permanent and global, did you want a bookmark?)
459 $ echo b > b
462 $ echo b > b
460 $ hg -R s up 3
463 $ hg -R s up 3
461 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
464 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
462 $ hg ci -Am1
465 $ hg ci -Am1
463 adding b
466 adding b
464 $ hg up default
467 $ hg up default
465 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
468 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
466 $ echo c > c
469 $ echo c > c
467 $ hg ci -Am1
470 $ hg ci -Am1
468 adding c
471 adding c
469 $ hg up 1
472 $ hg up 1
470 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
473 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
471 $ hg merge 2
474 $ hg merge 2
472 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
475 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
473 (branch merge, don't forget to commit)
476 (branch merge, don't forget to commit)
474 $ hg ci -m1
477 $ hg ci -m1
475 $ hg up 2
478 $ hg up 2
476 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
479 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
477 $ hg -R s up 4
480 $ hg -R s up 4
478 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
481 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
479 $ echo d > d
482 $ echo d > d
480 $ hg ci -Am1
483 $ hg ci -Am1
481 adding d
484 adding d
482 $ hg up 3
485 $ hg up 3
483 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
486 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
484 $ hg -R s up 5
487 $ hg -R s up 5
485 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
488 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
486 $ echo e > e
489 $ echo e > e
487 $ hg ci -Am1
490 $ hg ci -Am1
488 adding e
491 adding e
489
492
490 $ hg up 5
493 $ hg up 5
491 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
494 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
492 $ hg merge 4 # try to merge default into br again
495 $ hg merge 4 # try to merge default into br again
493 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
496 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
494 (branch merge, don't forget to commit)
497 (branch merge, don't forget to commit)
495 $ cd ..
498 $ cd ..
496
499
497 test subrepo delete from .hgsubstate
500 test subrepo delete from .hgsubstate
498
501
499 $ hg init testdelete
502 $ hg init testdelete
500 $ mkdir testdelete/nested testdelete/nested2
503 $ mkdir testdelete/nested testdelete/nested2
501 $ hg init testdelete/nested
504 $ hg init testdelete/nested
502 $ hg init testdelete/nested2
505 $ hg init testdelete/nested2
503 $ echo test > testdelete/nested/foo
506 $ echo test > testdelete/nested/foo
504 $ echo test > testdelete/nested2/foo
507 $ echo test > testdelete/nested2/foo
505 $ hg -R testdelete/nested add
508 $ hg -R testdelete/nested add
506 adding testdelete/nested/foo (glob)
509 adding testdelete/nested/foo (glob)
507 $ hg -R testdelete/nested2 add
510 $ hg -R testdelete/nested2 add
508 adding testdelete/nested2/foo (glob)
511 adding testdelete/nested2/foo (glob)
509 $ hg -R testdelete/nested ci -m test
512 $ hg -R testdelete/nested ci -m test
510 $ hg -R testdelete/nested2 ci -m test
513 $ hg -R testdelete/nested2 ci -m test
511 $ echo nested = nested > testdelete/.hgsub
514 $ echo nested = nested > testdelete/.hgsub
512 $ echo nested2 = nested2 >> testdelete/.hgsub
515 $ echo nested2 = nested2 >> testdelete/.hgsub
513 $ hg -R testdelete add
516 $ hg -R testdelete add
514 adding testdelete/.hgsub (glob)
517 adding testdelete/.hgsub (glob)
515 $ hg -R testdelete ci -m "nested 1 & 2 added"
518 $ hg -R testdelete ci -m "nested 1 & 2 added"
516 $ echo nested = nested > testdelete/.hgsub
519 $ echo nested = nested > testdelete/.hgsub
517 $ hg -R testdelete ci -m "nested 2 deleted"
520 $ hg -R testdelete ci -m "nested 2 deleted"
518 $ cat testdelete/.hgsubstate
521 $ cat testdelete/.hgsubstate
519 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
522 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
520 $ hg -R testdelete remove testdelete/.hgsub
523 $ hg -R testdelete remove testdelete/.hgsub
521 $ hg -R testdelete ci -m ".hgsub deleted"
524 $ hg -R testdelete ci -m ".hgsub deleted"
522 $ cat testdelete/.hgsubstate
525 $ cat testdelete/.hgsubstate
523 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
526 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
524
527
525 test repository cloning
528 test repository cloning
526
529
527 $ mkdir mercurial mercurial2
530 $ mkdir mercurial mercurial2
528 $ hg init nested_absolute
531 $ hg init nested_absolute
529 $ echo test > nested_absolute/foo
532 $ echo test > nested_absolute/foo
530 $ hg -R nested_absolute add
533 $ hg -R nested_absolute add
531 adding nested_absolute/foo (glob)
534 adding nested_absolute/foo (glob)
532 $ hg -R nested_absolute ci -mtest
535 $ hg -R nested_absolute ci -mtest
533 $ cd mercurial
536 $ cd mercurial
534 $ hg init nested_relative
537 $ hg init nested_relative
535 $ echo test2 > nested_relative/foo2
538 $ echo test2 > nested_relative/foo2
536 $ hg -R nested_relative add
539 $ hg -R nested_relative add
537 adding nested_relative/foo2 (glob)
540 adding nested_relative/foo2 (glob)
538 $ hg -R nested_relative ci -mtest2
541 $ hg -R nested_relative ci -mtest2
539 $ hg init main
542 $ hg init main
540 $ echo "nested_relative = ../nested_relative" > main/.hgsub
543 $ echo "nested_relative = ../nested_relative" > main/.hgsub
541 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
544 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
542 $ hg -R main add
545 $ hg -R main add
543 adding main/.hgsub (glob)
546 adding main/.hgsub (glob)
544 $ hg -R main ci -m "add subrepos"
547 $ hg -R main ci -m "add subrepos"
545 $ cd ..
548 $ cd ..
546 $ hg clone mercurial/main mercurial2/main
549 $ hg clone mercurial/main mercurial2/main
547 updating to branch default
550 updating to branch default
548 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
551 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
549 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
552 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
550 > mercurial2/main/nested_relative/.hg/hgrc
553 > mercurial2/main/nested_relative/.hg/hgrc
551 [paths]
554 [paths]
552 default = $TESTTMP/sub/mercurial/nested_absolute
555 default = $TESTTMP/sub/mercurial/nested_absolute
553 [paths]
556 [paths]
554 default = $TESTTMP/sub/mercurial/nested_relative
557 default = $TESTTMP/sub/mercurial/nested_relative
555 $ rm -rf mercurial mercurial2
558 $ rm -rf mercurial mercurial2
556
559
557 Issue1977: multirepo push should fail if subrepo push fails
560 Issue1977: multirepo push should fail if subrepo push fails
558
561
559 $ hg init repo
562 $ hg init repo
560 $ hg init repo/s
563 $ hg init repo/s
561 $ echo a > repo/s/a
564 $ echo a > repo/s/a
562 $ hg -R repo/s ci -Am0
565 $ hg -R repo/s ci -Am0
563 adding a
566 adding a
564 $ echo s = s > repo/.hgsub
567 $ echo s = s > repo/.hgsub
565 $ hg -R repo ci -Am1
568 $ hg -R repo ci -Am1
566 adding .hgsub
569 adding .hgsub
567 $ hg clone repo repo2
570 $ hg clone repo repo2
568 updating to branch default
571 updating to branch default
569 cloning subrepo s from $TESTTMP/sub/repo/s (glob)
572 cloning subrepo s from $TESTTMP/sub/repo/s (glob)
570 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
573 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
571 $ hg -q -R repo2 pull -u
574 $ hg -q -R repo2 pull -u
572 $ echo 1 > repo2/s/a
575 $ echo 1 > repo2/s/a
573 $ hg -R repo2/s ci -m2
576 $ hg -R repo2/s ci -m2
574 $ hg -q -R repo2/s push
577 $ hg -q -R repo2/s push
575 $ hg -R repo2/s up -C 0
578 $ hg -R repo2/s up -C 0
576 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
579 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
577 $ echo 2 > repo2/s/a
580 $ echo 2 > repo2/s/a
578 $ hg -R repo2/s ci -m3
581 $ hg -R repo2/s ci -m3
579 created new head
582 created new head
580 $ hg -R repo2 ci -m3
583 $ hg -R repo2 ci -m3
581 $ hg -q -R repo2 push
584 $ hg -q -R repo2 push
582 abort: push creates new remote head 9d66565e64e1!
585 abort: push creates new remote head 9d66565e64e1!
583 (did you forget to merge? use push -f to force)
586 (did you forget to merge? use push -f to force)
584 [255]
587 [255]
585 $ hg -R repo update
588 $ hg -R repo update
586 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
589 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
587 $ rm -rf repo2 repo
590 $ rm -rf repo2 repo
588
591
589
592
590 Issue1852 subrepos with relative paths always push/pull relative to default
593 Issue1852 subrepos with relative paths always push/pull relative to default
591
594
592 Prepare a repo with subrepo
595 Prepare a repo with subrepo
593
596
594 $ hg init issue1852a
597 $ hg init issue1852a
595 $ cd issue1852a
598 $ cd issue1852a
596 $ hg init sub/repo
599 $ hg init sub/repo
597 $ echo test > sub/repo/foo
600 $ echo test > sub/repo/foo
598 $ hg -R sub/repo add sub/repo/foo
601 $ hg -R sub/repo add sub/repo/foo
599 $ echo sub/repo = sub/repo > .hgsub
602 $ echo sub/repo = sub/repo > .hgsub
600 $ hg add .hgsub
603 $ hg add .hgsub
601 $ hg ci -mtest
604 $ hg ci -mtest
602 committing subrepository sub/repo (glob)
605 committing subrepository sub/repo (glob)
603 $ echo test >> sub/repo/foo
606 $ echo test >> sub/repo/foo
604 $ hg ci -mtest
607 $ hg ci -mtest
605 committing subrepository sub/repo (glob)
608 committing subrepository sub/repo (glob)
606 $ cd ..
609 $ cd ..
607
610
608 Create repo without default path, pull top repo, and see what happens on update
611 Create repo without default path, pull top repo, and see what happens on update
609
612
610 $ hg init issue1852b
613 $ hg init issue1852b
611 $ hg -R issue1852b pull issue1852a
614 $ hg -R issue1852b pull issue1852a
612 pulling from issue1852a
615 pulling from issue1852a
613 requesting all changes
616 requesting all changes
614 adding changesets
617 adding changesets
615 adding manifests
618 adding manifests
616 adding file changes
619 adding file changes
617 added 2 changesets with 3 changes to 2 files
620 added 2 changesets with 3 changes to 2 files
618 (run 'hg update' to get a working copy)
621 (run 'hg update' to get a working copy)
619 $ hg -R issue1852b update
622 $ hg -R issue1852b update
620 abort: default path for subrepository sub/repo not found (glob)
623 abort: default path for subrepository sub/repo not found (glob)
621 [255]
624 [255]
622
625
623 Pull -u now doesn't help
626 Pull -u now doesn't help
624
627
625 $ hg -R issue1852b pull -u issue1852a
628 $ hg -R issue1852b pull -u issue1852a
626 pulling from issue1852a
629 pulling from issue1852a
627 searching for changes
630 searching for changes
628 no changes found
631 no changes found
629
632
630 Try the same, but with pull -u
633 Try the same, but with pull -u
631
634
632 $ hg init issue1852c
635 $ hg init issue1852c
633 $ hg -R issue1852c pull -r0 -u issue1852a
636 $ hg -R issue1852c pull -r0 -u issue1852a
634 pulling from issue1852a
637 pulling from issue1852a
635 adding changesets
638 adding changesets
636 adding manifests
639 adding manifests
637 adding file changes
640 adding file changes
638 added 1 changesets with 2 changes to 2 files
641 added 1 changesets with 2 changes to 2 files
639 cloning subrepo sub/repo from issue1852a/sub/repo (glob)
642 cloning subrepo sub/repo from issue1852a/sub/repo (glob)
640 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
643 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
641
644
642 Try to push from the other side
645 Try to push from the other side
643
646
644 $ hg -R issue1852a push `pwd`/issue1852c
647 $ hg -R issue1852a push `pwd`/issue1852c
645 pushing to $TESTTMP/sub/issue1852c
648 pushing to $TESTTMP/sub/issue1852c
646 pushing subrepo sub/repo to $TESTTMP/sub/issue1852c/sub/repo (glob)
649 pushing subrepo sub/repo to $TESTTMP/sub/issue1852c/sub/repo (glob)
647 searching for changes
650 searching for changes
648 no changes found
651 no changes found
649 searching for changes
652 searching for changes
650 adding changesets
653 adding changesets
651 adding manifests
654 adding manifests
652 adding file changes
655 adding file changes
653 added 1 changesets with 1 changes to 1 files
656 added 1 changesets with 1 changes to 1 files
654
657
655 Incoming and outgoing should not use the default path:
658 Incoming and outgoing should not use the default path:
656
659
657 $ hg clone -q issue1852a issue1852d
660 $ hg clone -q issue1852a issue1852d
658 $ hg -R issue1852d outgoing --subrepos issue1852c
661 $ hg -R issue1852d outgoing --subrepos issue1852c
659 comparing with issue1852c
662 comparing with issue1852c
660 searching for changes
663 searching for changes
661 no changes found
664 no changes found
662 comparing with issue1852c/sub/repo
665 comparing with issue1852c/sub/repo
663 searching for changes
666 searching for changes
664 no changes found
667 no changes found
665 [1]
668 [1]
666 $ hg -R issue1852d incoming --subrepos issue1852c
669 $ hg -R issue1852d incoming --subrepos issue1852c
667 comparing with issue1852c
670 comparing with issue1852c
668 searching for changes
671 searching for changes
669 no changes found
672 no changes found
670 comparing with issue1852c/sub/repo
673 comparing with issue1852c/sub/repo
671 searching for changes
674 searching for changes
672 no changes found
675 no changes found
673 [1]
676 [1]
674
677
675 Check status of files when none of them belong to the first
678 Check status of files when none of them belong to the first
676 subrepository:
679 subrepository:
677
680
678 $ hg init subrepo-status
681 $ hg init subrepo-status
679 $ cd subrepo-status
682 $ cd subrepo-status
680 $ hg init subrepo-1
683 $ hg init subrepo-1
681 $ hg init subrepo-2
684 $ hg init subrepo-2
682 $ cd subrepo-2
685 $ cd subrepo-2
683 $ touch file
686 $ touch file
684 $ hg add file
687 $ hg add file
685 $ cd ..
688 $ cd ..
686 $ echo subrepo-1 = subrepo-1 > .hgsub
689 $ echo subrepo-1 = subrepo-1 > .hgsub
687 $ echo subrepo-2 = subrepo-2 >> .hgsub
690 $ echo subrepo-2 = subrepo-2 >> .hgsub
688 $ hg add .hgsub
691 $ hg add .hgsub
689 $ hg ci -m 'Added subrepos'
692 $ hg ci -m 'Added subrepos'
690 committing subrepository subrepo-2
693 committing subrepository subrepo-2
691 $ hg st subrepo-2/file
694 $ hg st subrepo-2/file
692
695
693 Check hg update --clean
696 Check hg update --clean
694 $ cd $TESTTMP/sub/t
697 $ cd $TESTTMP/sub/t
695 $ rm -r t/t.orig
698 $ rm -r t/t.orig
696 $ hg status -S --all
699 $ hg status -S --all
697 C .hgsub
700 C .hgsub
698 C .hgsubstate
701 C .hgsubstate
699 C a
702 C a
700 C s/.hgsub
703 C s/.hgsub
701 C s/.hgsubstate
704 C s/.hgsubstate
702 C s/a
705 C s/a
703 C s/ss/a
706 C s/ss/a
704 C t/t
707 C t/t
705 $ echo c1 > s/a
708 $ echo c1 > s/a
706 $ cd s
709 $ cd s
707 $ echo c1 > b
710 $ echo c1 > b
708 $ echo c1 > c
711 $ echo c1 > c
709 $ hg add b
712 $ hg add b
710 $ cd ..
713 $ cd ..
711 $ hg status -S
714 $ hg status -S
712 M s/a
715 M s/a
713 A s/b
716 A s/b
714 ? s/c
717 ? s/c
715 $ hg update -C
718 $ hg update -C
716 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
719 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
717 $ hg status -S
720 $ hg status -S
718 ? s/b
721 ? s/b
719 ? s/c
722 ? s/c
720
723
721 Sticky subrepositories, no changes
724 Sticky subrepositories, no changes
722 $ cd $TESTTMP/sub/t
725 $ cd $TESTTMP/sub/t
723 $ hg id
726 $ hg id
724 925c17564ef8 tip
727 925c17564ef8 tip
725 $ hg -R s id
728 $ hg -R s id
726 12a213df6fa9 tip
729 12a213df6fa9 tip
727 $ hg -R t id
730 $ hg -R t id
728 52c0adc0515a tip
731 52c0adc0515a tip
729 $ hg update 11
732 $ hg update 11
730 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
733 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
731 $ hg id
734 $ hg id
732 365661e5936a
735 365661e5936a
733 $ hg -R s id
736 $ hg -R s id
734 fc627a69481f
737 fc627a69481f
735 $ hg -R t id
738 $ hg -R t id
736 e95bcfa18a35
739 e95bcfa18a35
737
740
738 Sticky subrepositorys, file changes
741 Sticky subrepositorys, file changes
739 $ touch s/f1
742 $ touch s/f1
740 $ touch t/f1
743 $ touch t/f1
741 $ hg add -S s/f1
744 $ hg add -S s/f1
742 $ hg add -S t/f1
745 $ hg add -S t/f1
743 $ hg id
746 $ hg id
744 365661e5936a
747 365661e5936a
745 $ hg -R s id
748 $ hg -R s id
746 fc627a69481f+
749 fc627a69481f+
747 $ hg -R t id
750 $ hg -R t id
748 e95bcfa18a35+
751 e95bcfa18a35+
749 $ hg update tip
752 $ hg update tip
750 subrepository sources for s differ
753 subrepository sources for s differ
751 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)?
754 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)?
752 l
755 l
753 subrepository sources for t differ
756 subrepository sources for t differ
754 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)?
757 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)?
755 l
758 l
756 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
759 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
757 $ hg id
760 $ hg id
758 925c17564ef8+ tip
761 925c17564ef8+ tip
759 $ hg -R s id
762 $ hg -R s id
760 fc627a69481f+
763 fc627a69481f+
761 $ hg -R t id
764 $ hg -R t id
762 e95bcfa18a35+
765 e95bcfa18a35+
763 $ hg update --clean tip
766 $ hg update --clean tip
764 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
767 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
765
768
766 Sticky subrepository, revision updates
769 Sticky subrepository, revision updates
767 $ hg id
770 $ hg id
768 925c17564ef8 tip
771 925c17564ef8 tip
769 $ hg -R s id
772 $ hg -R s id
770 12a213df6fa9 tip
773 12a213df6fa9 tip
771 $ hg -R t id
774 $ hg -R t id
772 52c0adc0515a tip
775 52c0adc0515a tip
773 $ cd s
776 $ cd s
774 $ hg update -r -2
777 $ hg update -r -2
775 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
778 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
776 $ cd ../t
779 $ cd ../t
777 $ hg update -r 2
780 $ hg update -r 2
778 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
781 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
779 $ cd ..
782 $ cd ..
780 $ hg update 10
783 $ hg update 10
781 subrepository sources for t differ (in checked out version)
784 subrepository sources for t differ (in checked out version)
782 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)?
785 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)?
783 l
786 l
784 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
787 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
785 $ hg id
788 $ hg id
786 e45c8b14af55+
789 e45c8b14af55+
787 $ hg -R s id
790 $ hg -R s id
788 1c833a7a9e3a
791 1c833a7a9e3a
789 $ hg -R t id
792 $ hg -R t id
790 7af322bc1198
793 7af322bc1198
791
794
792 Sticky subrepository, file changes and revision updates
795 Sticky subrepository, file changes and revision updates
793 $ touch s/f1
796 $ touch s/f1
794 $ touch t/f1
797 $ touch t/f1
795 $ hg add -S s/f1
798 $ hg add -S s/f1
796 $ hg add -S t/f1
799 $ hg add -S t/f1
797 $ hg id
800 $ hg id
798 e45c8b14af55+
801 e45c8b14af55+
799 $ hg -R s id
802 $ hg -R s id
800 1c833a7a9e3a+
803 1c833a7a9e3a+
801 $ hg -R t id
804 $ hg -R t id
802 7af322bc1198+
805 7af322bc1198+
803 $ hg update tip
806 $ hg update tip
804 subrepository sources for s differ
807 subrepository sources for s differ
805 use (l)ocal source (1c833a7a9e3a) or (r)emote source (12a213df6fa9)?
808 use (l)ocal source (1c833a7a9e3a) or (r)emote source (12a213df6fa9)?
806 l
809 l
807 subrepository sources for t differ
810 subrepository sources for t differ
808 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)?
811 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)?
809 l
812 l
810 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
813 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
811 $ hg id
814 $ hg id
812 925c17564ef8 tip
815 925c17564ef8 tip
813 $ hg -R s id
816 $ hg -R s id
814 1c833a7a9e3a+
817 1c833a7a9e3a+
815 $ hg -R t id
818 $ hg -R t id
816 7af322bc1198+
819 7af322bc1198+
817
820
818 Sticky repository, update --clean
821 Sticky repository, update --clean
819 $ hg update --clean tip
822 $ hg update --clean tip
820 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
823 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
821 $ hg id
824 $ hg id
822 925c17564ef8 tip
825 925c17564ef8 tip
823 $ hg -R s id
826 $ hg -R s id
824 12a213df6fa9 tip
827 12a213df6fa9 tip
825 $ hg -R t id
828 $ hg -R t id
826 52c0adc0515a tip
829 52c0adc0515a tip
827
830
828 Test subrepo already at intended revision:
831 Test subrepo already at intended revision:
829 $ cd s
832 $ cd s
830 $ hg update fc627a69481f
833 $ hg update fc627a69481f
831 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
834 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
832 $ cd ..
835 $ cd ..
833 $ hg update 11
836 $ hg update 11
834 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
837 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
835 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
838 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
836 $ hg id -n
839 $ hg id -n
837 11+
840 11+
838 $ hg -R s id
841 $ hg -R s id
839 fc627a69481f
842 fc627a69481f
840 $ hg -R t id
843 $ hg -R t id
841 e95bcfa18a35
844 e95bcfa18a35
842
845
843 Test that removing .hgsubstate doesn't break anything:
846 Test that removing .hgsubstate doesn't break anything:
844
847
845 $ hg rm -f .hgsubstate
848 $ hg rm -f .hgsubstate
846 $ hg ci -mrm
849 $ hg ci -mrm
847 nothing changed
850 nothing changed
848 [1]
851 [1]
849 $ hg log -vr tip
852 $ hg log -vr tip
850 changeset: 13:925c17564ef8
853 changeset: 13:925c17564ef8
851 tag: tip
854 tag: tip
852 user: test
855 user: test
853 date: Thu Jan 01 00:00:00 1970 +0000
856 date: Thu Jan 01 00:00:00 1970 +0000
854 files: .hgsubstate
857 files: .hgsubstate
855 description:
858 description:
856 13
859 13
857
860
858
861
859
862
860 Test that removing .hgsub removes .hgsubstate:
863 Test that removing .hgsub removes .hgsubstate:
861
864
862 $ hg rm .hgsub
865 $ hg rm .hgsub
863 $ hg ci -mrm2
866 $ hg ci -mrm2
864 created new head
867 created new head
865 $ hg log -vr tip
868 $ hg log -vr tip
866 changeset: 14:2400bccd50af
869 changeset: 14:2400bccd50af
867 tag: tip
870 tag: tip
868 parent: 11:365661e5936a
871 parent: 11:365661e5936a
869 user: test
872 user: test
870 date: Thu Jan 01 00:00:00 1970 +0000
873 date: Thu Jan 01 00:00:00 1970 +0000
871 files: .hgsub .hgsubstate
874 files: .hgsub .hgsubstate
872 description:
875 description:
873 rm2
876 rm2
874
877
875
878
876 Test issue3153: diff -S with deleted subrepos
879 Test issue3153: diff -S with deleted subrepos
877
880
878 $ hg diff --nodates -S -c .
881 $ hg diff --nodates -S -c .
879 diff -r 365661e5936a -r 2400bccd50af .hgsub
882 diff -r 365661e5936a -r 2400bccd50af .hgsub
880 --- a/.hgsub
883 --- a/.hgsub
881 +++ /dev/null
884 +++ /dev/null
882 @@ -1,2 +0,0 @@
885 @@ -1,2 +0,0 @@
883 -s = s
886 -s = s
884 -t = t
887 -t = t
885 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
888 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
886 --- a/.hgsubstate
889 --- a/.hgsubstate
887 +++ /dev/null
890 +++ /dev/null
888 @@ -1,2 +0,0 @@
891 @@ -1,2 +0,0 @@
889 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
892 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
890 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
893 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
891
894
892 Test behavior of add for explicit path in subrepo:
895 Test behavior of add for explicit path in subrepo:
893 $ cd ..
896 $ cd ..
894 $ hg init explicit
897 $ hg init explicit
895 $ cd explicit
898 $ cd explicit
896 $ echo s = s > .hgsub
899 $ echo s = s > .hgsub
897 $ hg add .hgsub
900 $ hg add .hgsub
898 $ hg init s
901 $ hg init s
899 $ hg ci -m0
902 $ hg ci -m0
900 Adding with an explicit path in a subrepo adds the file
903 Adding with an explicit path in a subrepo adds the file
901 $ echo c1 > f1
904 $ echo c1 > f1
902 $ echo c2 > s/f2
905 $ echo c2 > s/f2
903 $ hg st -S
906 $ hg st -S
904 ? f1
907 ? f1
905 ? s/f2
908 ? s/f2
906 $ hg add s/f2
909 $ hg add s/f2
907 $ hg st -S
910 $ hg st -S
908 A s/f2
911 A s/f2
909 ? f1
912 ? f1
910 $ hg ci -R s -m0
913 $ hg ci -R s -m0
911 $ hg ci -Am1
914 $ hg ci -Am1
912 adding f1
915 adding f1
913 Adding with an explicit path in a subrepo with -S has the same behavior
916 Adding with an explicit path in a subrepo with -S has the same behavior
914 $ echo c3 > f3
917 $ echo c3 > f3
915 $ echo c4 > s/f4
918 $ echo c4 > s/f4
916 $ hg st -S
919 $ hg st -S
917 ? f3
920 ? f3
918 ? s/f4
921 ? s/f4
919 $ hg add -S s/f4
922 $ hg add -S s/f4
920 $ hg st -S
923 $ hg st -S
921 A s/f4
924 A s/f4
922 ? f3
925 ? f3
923 $ hg ci -R s -m1
926 $ hg ci -R s -m1
924 $ hg ci -Ama2
927 $ hg ci -Ama2
925 adding f3
928 adding f3
926 Adding without a path or pattern silently ignores subrepos
929 Adding without a path or pattern silently ignores subrepos
927 $ echo c5 > f5
930 $ echo c5 > f5
928 $ echo c6 > s/f6
931 $ echo c6 > s/f6
929 $ echo c7 > s/f7
932 $ echo c7 > s/f7
930 $ hg st -S
933 $ hg st -S
931 ? f5
934 ? f5
932 ? s/f6
935 ? s/f6
933 ? s/f7
936 ? s/f7
934 $ hg add
937 $ hg add
935 adding f5
938 adding f5
936 $ hg st -S
939 $ hg st -S
937 A f5
940 A f5
938 ? s/f6
941 ? s/f6
939 ? s/f7
942 ? s/f7
940 $ hg ci -R s -Am2
943 $ hg ci -R s -Am2
941 adding f6
944 adding f6
942 adding f7
945 adding f7
943 $ hg ci -m3
946 $ hg ci -m3
944 Adding without a path or pattern with -S also adds files in subrepos
947 Adding without a path or pattern with -S also adds files in subrepos
945 $ echo c8 > f8
948 $ echo c8 > f8
946 $ echo c9 > s/f9
949 $ echo c9 > s/f9
947 $ echo c10 > s/f10
950 $ echo c10 > s/f10
948 $ hg st -S
951 $ hg st -S
949 ? f8
952 ? f8
950 ? s/f10
953 ? s/f10
951 ? s/f9
954 ? s/f9
952 $ hg add -S
955 $ hg add -S
953 adding f8
956 adding f8
954 adding s/f10 (glob)
957 adding s/f10 (glob)
955 adding s/f9 (glob)
958 adding s/f9 (glob)
956 $ hg st -S
959 $ hg st -S
957 A f8
960 A f8
958 A s/f10
961 A s/f10
959 A s/f9
962 A s/f9
960 $ hg ci -R s -m3
963 $ hg ci -R s -m3
961 $ hg ci -m4
964 $ hg ci -m4
962 Adding with a pattern silently ignores subrepos
965 Adding with a pattern silently ignores subrepos
963 $ echo c11 > fm11
966 $ echo c11 > fm11
964 $ echo c12 > fn12
967 $ echo c12 > fn12
965 $ echo c13 > s/fm13
968 $ echo c13 > s/fm13
966 $ echo c14 > s/fn14
969 $ echo c14 > s/fn14
967 $ hg st -S
970 $ hg st -S
968 ? fm11
971 ? fm11
969 ? fn12
972 ? fn12
970 ? s/fm13
973 ? s/fm13
971 ? s/fn14
974 ? s/fn14
972 $ hg add 'glob:**fm*'
975 $ hg add 'glob:**fm*'
973 adding fm11
976 adding fm11
974 $ hg st -S
977 $ hg st -S
975 A fm11
978 A fm11
976 ? fn12
979 ? fn12
977 ? s/fm13
980 ? s/fm13
978 ? s/fn14
981 ? s/fn14
979 $ hg ci -R s -Am4
982 $ hg ci -R s -Am4
980 adding fm13
983 adding fm13
981 adding fn14
984 adding fn14
982 $ hg ci -Am5
985 $ hg ci -Am5
983 adding fn12
986 adding fn12
984 Adding with a pattern with -S also adds matches in subrepos
987 Adding with a pattern with -S also adds matches in subrepos
985 $ echo c15 > fm15
988 $ echo c15 > fm15
986 $ echo c16 > fn16
989 $ echo c16 > fn16
987 $ echo c17 > s/fm17
990 $ echo c17 > s/fm17
988 $ echo c18 > s/fn18
991 $ echo c18 > s/fn18
989 $ hg st -S
992 $ hg st -S
990 ? fm15
993 ? fm15
991 ? fn16
994 ? fn16
992 ? s/fm17
995 ? s/fm17
993 ? s/fn18
996 ? s/fn18
994 $ hg add -S 'glob:**fm*'
997 $ hg add -S 'glob:**fm*'
995 adding fm15
998 adding fm15
996 adding s/fm17 (glob)
999 adding s/fm17 (glob)
997 $ hg st -S
1000 $ hg st -S
998 A fm15
1001 A fm15
999 A s/fm17
1002 A s/fm17
1000 ? fn16
1003 ? fn16
1001 ? s/fn18
1004 ? s/fn18
1002 $ hg ci -R s -Am5
1005 $ hg ci -R s -Am5
1003 adding fn18
1006 adding fn18
1004 $ hg ci -Am6
1007 $ hg ci -Am6
1005 adding fn16
1008 adding fn16
1006
1009
1007 Test behavior of forget for explicit path in subrepo:
1010 Test behavior of forget for explicit path in subrepo:
1008 Forgetting an explicit path in a subrepo untracks the file
1011 Forgetting an explicit path in a subrepo untracks the file
1009 $ echo c19 > s/f19
1012 $ echo c19 > s/f19
1010 $ hg add s/f19
1013 $ hg add s/f19
1011 $ hg st -S
1014 $ hg st -S
1012 A s/f19
1015 A s/f19
1013 $ hg forget s/f19
1016 $ hg forget s/f19
1014 $ hg st -S
1017 $ hg st -S
1015 ? s/f19
1018 ? s/f19
1016 $ rm s/f19
1019 $ rm s/f19
General Comments 0
You need to be logged in to leave comments. Login now