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