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