##// END OF EJS Templates
subrepos: handle diff nodeids in subrepos, not before...
Patrick Mezard -
r12209:affec9fb default
parent child Browse files
Show More
@@ -1,1293 +1,1293 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, bin, 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, glob, tempfile
10 import os, sys, errno, re, glob, tempfile
11 import util, templater, patch, error, encoding, templatekw
11 import util, templater, patch, error, encoding, templatekw
12 import match as matchmod
12 import match as matchmod
13 import similar, revset, subrepo
13 import similar, revset, subrepo
14
14
15 revrangesep = ':'
15 revrangesep = ':'
16
16
17 def parsealiases(cmd):
17 def parsealiases(cmd):
18 return cmd.lstrip("^").split("|")
18 return cmd.lstrip("^").split("|")
19
19
20 def findpossible(cmd, table, strict=False):
20 def findpossible(cmd, table, strict=False):
21 """
21 """
22 Return cmd -> (aliases, command table entry)
22 Return cmd -> (aliases, command table entry)
23 for each matching command.
23 for each matching command.
24 Return debug commands (or their aliases) only if no normal command matches.
24 Return debug commands (or their aliases) only if no normal command matches.
25 """
25 """
26 choice = {}
26 choice = {}
27 debugchoice = {}
27 debugchoice = {}
28 for e in table.keys():
28 for e in table.keys():
29 aliases = parsealiases(e)
29 aliases = parsealiases(e)
30 found = None
30 found = None
31 if cmd in aliases:
31 if cmd in aliases:
32 found = cmd
32 found = cmd
33 elif not strict:
33 elif not strict:
34 for a in aliases:
34 for a in aliases:
35 if a.startswith(cmd):
35 if a.startswith(cmd):
36 found = a
36 found = a
37 break
37 break
38 if found is not None:
38 if found is not None:
39 if aliases[0].startswith("debug") or found.startswith("debug"):
39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 debugchoice[found] = (aliases, table[e])
40 debugchoice[found] = (aliases, table[e])
41 else:
41 else:
42 choice[found] = (aliases, table[e])
42 choice[found] = (aliases, table[e])
43
43
44 if not choice and debugchoice:
44 if not choice and debugchoice:
45 choice = debugchoice
45 choice = debugchoice
46
46
47 return choice
47 return choice
48
48
49 def findcmd(cmd, table, strict=True):
49 def findcmd(cmd, table, strict=True):
50 """Return (aliases, command table entry) for command string."""
50 """Return (aliases, command table entry) for command string."""
51 choice = findpossible(cmd, table, strict)
51 choice = findpossible(cmd, table, strict)
52
52
53 if cmd in choice:
53 if cmd in choice:
54 return choice[cmd]
54 return choice[cmd]
55
55
56 if len(choice) > 1:
56 if len(choice) > 1:
57 clist = choice.keys()
57 clist = choice.keys()
58 clist.sort()
58 clist.sort()
59 raise error.AmbiguousCommand(cmd, clist)
59 raise error.AmbiguousCommand(cmd, clist)
60
60
61 if choice:
61 if choice:
62 return choice.values()[0]
62 return choice.values()[0]
63
63
64 raise error.UnknownCommand(cmd)
64 raise error.UnknownCommand(cmd)
65
65
66 def findrepo(p):
66 def findrepo(p):
67 while not os.path.isdir(os.path.join(p, ".hg")):
67 while not os.path.isdir(os.path.join(p, ".hg")):
68 oldp, p = p, os.path.dirname(p)
68 oldp, p = p, os.path.dirname(p)
69 if p == oldp:
69 if p == oldp:
70 return None
70 return None
71
71
72 return p
72 return p
73
73
74 def bail_if_changed(repo):
74 def bail_if_changed(repo):
75 if repo.dirstate.parents()[1] != nullid:
75 if repo.dirstate.parents()[1] != nullid:
76 raise util.Abort(_('outstanding uncommitted merge'))
76 raise util.Abort(_('outstanding uncommitted merge'))
77 modified, added, removed, deleted = repo.status()[:4]
77 modified, added, removed, deleted = repo.status()[:4]
78 if modified or added or removed or deleted:
78 if modified or added or removed or deleted:
79 raise util.Abort(_("outstanding uncommitted changes"))
79 raise util.Abort(_("outstanding uncommitted changes"))
80
80
81 def logmessage(opts):
81 def logmessage(opts):
82 """ get the log message according to -m and -l option """
82 """ get the log message according to -m and -l option """
83 message = opts.get('message')
83 message = opts.get('message')
84 logfile = opts.get('logfile')
84 logfile = opts.get('logfile')
85
85
86 if message and logfile:
86 if message and logfile:
87 raise util.Abort(_('options --message and --logfile are mutually '
87 raise util.Abort(_('options --message and --logfile are mutually '
88 'exclusive'))
88 'exclusive'))
89 if not message and logfile:
89 if not message and logfile:
90 try:
90 try:
91 if logfile == '-':
91 if logfile == '-':
92 message = sys.stdin.read()
92 message = sys.stdin.read()
93 else:
93 else:
94 message = open(logfile).read()
94 message = open(logfile).read()
95 except IOError, inst:
95 except IOError, inst:
96 raise util.Abort(_("can't read commit message '%s': %s") %
96 raise util.Abort(_("can't read commit message '%s': %s") %
97 (logfile, inst.strerror))
97 (logfile, inst.strerror))
98 return message
98 return message
99
99
100 def loglimit(opts):
100 def loglimit(opts):
101 """get the log limit according to option -l/--limit"""
101 """get the log limit according to option -l/--limit"""
102 limit = opts.get('limit')
102 limit = opts.get('limit')
103 if limit:
103 if limit:
104 try:
104 try:
105 limit = int(limit)
105 limit = int(limit)
106 except ValueError:
106 except ValueError:
107 raise util.Abort(_('limit must be a positive integer'))
107 raise util.Abort(_('limit must be a positive integer'))
108 if limit <= 0:
108 if limit <= 0:
109 raise util.Abort(_('limit must be positive'))
109 raise util.Abort(_('limit must be positive'))
110 else:
110 else:
111 limit = None
111 limit = None
112 return limit
112 return limit
113
113
114 def revpair(repo, revs):
114 def revpair(repo, revs):
115 '''return pair of nodes, given list of revisions. second item can
115 '''return pair of nodes, given list of revisions. second item can
116 be None, meaning use working dir.'''
116 be None, meaning use working dir.'''
117
117
118 def revfix(repo, val, defval):
118 def revfix(repo, val, defval):
119 if not val and val != 0 and defval is not None:
119 if not val and val != 0 and defval is not None:
120 val = defval
120 val = defval
121 return repo.lookup(val)
121 return repo.lookup(val)
122
122
123 if not revs:
123 if not revs:
124 return repo.dirstate.parents()[0], None
124 return repo.dirstate.parents()[0], None
125 end = None
125 end = None
126 if len(revs) == 1:
126 if len(revs) == 1:
127 if revrangesep in revs[0]:
127 if revrangesep in revs[0]:
128 start, end = revs[0].split(revrangesep, 1)
128 start, end = revs[0].split(revrangesep, 1)
129 start = revfix(repo, start, 0)
129 start = revfix(repo, start, 0)
130 end = revfix(repo, end, len(repo) - 1)
130 end = revfix(repo, end, len(repo) - 1)
131 else:
131 else:
132 start = revfix(repo, revs[0], None)
132 start = revfix(repo, revs[0], None)
133 elif len(revs) == 2:
133 elif len(revs) == 2:
134 if revrangesep in revs[0] or revrangesep in revs[1]:
134 if revrangesep in revs[0] or revrangesep in revs[1]:
135 raise util.Abort(_('too many revisions specified'))
135 raise util.Abort(_('too many revisions specified'))
136 start = revfix(repo, revs[0], None)
136 start = revfix(repo, revs[0], None)
137 end = revfix(repo, revs[1], None)
137 end = revfix(repo, revs[1], None)
138 else:
138 else:
139 raise util.Abort(_('too many revisions specified'))
139 raise util.Abort(_('too many revisions specified'))
140 return start, end
140 return start, end
141
141
142 def revrange(repo, revs):
142 def revrange(repo, revs):
143 """Yield revision as strings from a list of revision specifications."""
143 """Yield revision as strings from a list of revision specifications."""
144
144
145 def revfix(repo, val, defval):
145 def revfix(repo, val, defval):
146 if not val and val != 0 and defval is not None:
146 if not val and val != 0 and defval is not None:
147 return defval
147 return defval
148 return repo.changelog.rev(repo.lookup(val))
148 return repo.changelog.rev(repo.lookup(val))
149
149
150 seen, l = set(), []
150 seen, l = set(), []
151 for spec in revs:
151 for spec in revs:
152 # attempt to parse old-style ranges first to deal with
152 # attempt to parse old-style ranges first to deal with
153 # things like old-tag which contain query metacharacters
153 # things like old-tag which contain query metacharacters
154 try:
154 try:
155 if revrangesep in spec:
155 if revrangesep in spec:
156 start, end = spec.split(revrangesep, 1)
156 start, end = spec.split(revrangesep, 1)
157 start = revfix(repo, start, 0)
157 start = revfix(repo, start, 0)
158 end = revfix(repo, end, len(repo) - 1)
158 end = revfix(repo, end, len(repo) - 1)
159 step = start > end and -1 or 1
159 step = start > end and -1 or 1
160 for rev in xrange(start, end + step, step):
160 for rev in xrange(start, end + step, step):
161 if rev in seen:
161 if rev in seen:
162 continue
162 continue
163 seen.add(rev)
163 seen.add(rev)
164 l.append(rev)
164 l.append(rev)
165 continue
165 continue
166 elif spec and spec in repo: # single unquoted rev
166 elif spec and spec in repo: # single unquoted rev
167 rev = revfix(repo, spec, None)
167 rev = revfix(repo, spec, None)
168 if rev in seen:
168 if rev in seen:
169 continue
169 continue
170 seen.add(rev)
170 seen.add(rev)
171 l.append(rev)
171 l.append(rev)
172 continue
172 continue
173 except error.RepoLookupError:
173 except error.RepoLookupError:
174 pass
174 pass
175
175
176 # fall through to new-style queries if old-style fails
176 # fall through to new-style queries if old-style fails
177 m = revset.match(spec)
177 m = revset.match(spec)
178 for r in m(repo, range(len(repo))):
178 for r in m(repo, range(len(repo))):
179 if r not in seen:
179 if r not in seen:
180 l.append(r)
180 l.append(r)
181 seen.update(l)
181 seen.update(l)
182
182
183 return l
183 return l
184
184
185 def make_filename(repo, pat, node,
185 def make_filename(repo, pat, node,
186 total=None, seqno=None, revwidth=None, pathname=None):
186 total=None, seqno=None, revwidth=None, pathname=None):
187 node_expander = {
187 node_expander = {
188 'H': lambda: hex(node),
188 'H': lambda: hex(node),
189 'R': lambda: str(repo.changelog.rev(node)),
189 'R': lambda: str(repo.changelog.rev(node)),
190 'h': lambda: short(node),
190 'h': lambda: short(node),
191 }
191 }
192 expander = {
192 expander = {
193 '%': lambda: '%',
193 '%': lambda: '%',
194 'b': lambda: os.path.basename(repo.root),
194 'b': lambda: os.path.basename(repo.root),
195 }
195 }
196
196
197 try:
197 try:
198 if node:
198 if node:
199 expander.update(node_expander)
199 expander.update(node_expander)
200 if node:
200 if node:
201 expander['r'] = (lambda:
201 expander['r'] = (lambda:
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
203 if total is not None:
203 if total is not None:
204 expander['N'] = lambda: str(total)
204 expander['N'] = lambda: str(total)
205 if seqno is not None:
205 if seqno is not None:
206 expander['n'] = lambda: str(seqno)
206 expander['n'] = lambda: str(seqno)
207 if total is not None and seqno is not None:
207 if total is not None and seqno is not None:
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
209 if pathname is not None:
209 if pathname is not None:
210 expander['s'] = lambda: os.path.basename(pathname)
210 expander['s'] = lambda: os.path.basename(pathname)
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
212 expander['p'] = lambda: pathname
212 expander['p'] = lambda: pathname
213
213
214 newname = []
214 newname = []
215 patlen = len(pat)
215 patlen = len(pat)
216 i = 0
216 i = 0
217 while i < patlen:
217 while i < patlen:
218 c = pat[i]
218 c = pat[i]
219 if c == '%':
219 if c == '%':
220 i += 1
220 i += 1
221 c = pat[i]
221 c = pat[i]
222 c = expander[c]()
222 c = expander[c]()
223 newname.append(c)
223 newname.append(c)
224 i += 1
224 i += 1
225 return ''.join(newname)
225 return ''.join(newname)
226 except KeyError, inst:
226 except KeyError, inst:
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
228 inst.args[0])
228 inst.args[0])
229
229
230 def make_file(repo, pat, node=None,
230 def make_file(repo, pat, node=None,
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
232
232
233 writable = 'w' in mode or 'a' in mode
233 writable = 'w' in mode or 'a' in mode
234
234
235 if not pat or pat == '-':
235 if not pat or pat == '-':
236 return writable and sys.stdout or sys.stdin
236 return writable and sys.stdout or sys.stdin
237 if hasattr(pat, 'write') and writable:
237 if hasattr(pat, 'write') and writable:
238 return pat
238 return pat
239 if hasattr(pat, 'read') and 'r' in mode:
239 if hasattr(pat, 'read') and 'r' in mode:
240 return pat
240 return pat
241 return open(make_filename(repo, pat, node, total, seqno, revwidth,
241 return open(make_filename(repo, pat, node, total, seqno, revwidth,
242 pathname),
242 pathname),
243 mode)
243 mode)
244
244
245 def expandpats(pats):
245 def expandpats(pats):
246 if not util.expandglobs:
246 if not util.expandglobs:
247 return list(pats)
247 return list(pats)
248 ret = []
248 ret = []
249 for p in pats:
249 for p in pats:
250 kind, name = matchmod._patsplit(p, None)
250 kind, name = matchmod._patsplit(p, None)
251 if kind is None:
251 if kind is None:
252 try:
252 try:
253 globbed = glob.glob(name)
253 globbed = glob.glob(name)
254 except re.error:
254 except re.error:
255 globbed = [name]
255 globbed = [name]
256 if globbed:
256 if globbed:
257 ret.extend(globbed)
257 ret.extend(globbed)
258 continue
258 continue
259 ret.append(p)
259 ret.append(p)
260 return ret
260 return ret
261
261
262 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
262 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
263 if not globbed and default == 'relpath':
263 if not globbed and default == 'relpath':
264 pats = expandpats(pats or [])
264 pats = expandpats(pats or [])
265 m = matchmod.match(repo.root, repo.getcwd(), pats,
265 m = matchmod.match(repo.root, repo.getcwd(), pats,
266 opts.get('include'), opts.get('exclude'), default,
266 opts.get('include'), opts.get('exclude'), default,
267 auditor=repo.auditor)
267 auditor=repo.auditor)
268 def badfn(f, msg):
268 def badfn(f, msg):
269 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
269 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
270 m.bad = badfn
270 m.bad = badfn
271 return m
271 return m
272
272
273 def matchall(repo):
273 def matchall(repo):
274 return matchmod.always(repo.root, repo.getcwd())
274 return matchmod.always(repo.root, repo.getcwd())
275
275
276 def matchfiles(repo, files):
276 def matchfiles(repo, files):
277 return matchmod.exact(repo.root, repo.getcwd(), files)
277 return matchmod.exact(repo.root, repo.getcwd(), files)
278
278
279 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
279 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
280 if dry_run is None:
280 if dry_run is None:
281 dry_run = opts.get('dry_run')
281 dry_run = opts.get('dry_run')
282 if similarity is None:
282 if similarity is None:
283 similarity = float(opts.get('similarity') or 0)
283 similarity = float(opts.get('similarity') or 0)
284 # we'd use status here, except handling of symlinks and ignore is tricky
284 # we'd use status here, except handling of symlinks and ignore is tricky
285 added, unknown, deleted, removed = [], [], [], []
285 added, unknown, deleted, removed = [], [], [], []
286 audit_path = util.path_auditor(repo.root)
286 audit_path = util.path_auditor(repo.root)
287 m = match(repo, pats, opts)
287 m = match(repo, pats, opts)
288 for abs in repo.walk(m):
288 for abs in repo.walk(m):
289 target = repo.wjoin(abs)
289 target = repo.wjoin(abs)
290 good = True
290 good = True
291 try:
291 try:
292 audit_path(abs)
292 audit_path(abs)
293 except:
293 except:
294 good = False
294 good = False
295 rel = m.rel(abs)
295 rel = m.rel(abs)
296 exact = m.exact(abs)
296 exact = m.exact(abs)
297 if good and abs not in repo.dirstate:
297 if good and abs not in repo.dirstate:
298 unknown.append(abs)
298 unknown.append(abs)
299 if repo.ui.verbose or not exact:
299 if repo.ui.verbose or not exact:
300 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
300 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
301 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
301 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
302 or (os.path.isdir(target) and not os.path.islink(target))):
302 or (os.path.isdir(target) and not os.path.islink(target))):
303 deleted.append(abs)
303 deleted.append(abs)
304 if repo.ui.verbose or not exact:
304 if repo.ui.verbose or not exact:
305 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
305 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
306 # for finding renames
306 # for finding renames
307 elif repo.dirstate[abs] == 'r':
307 elif repo.dirstate[abs] == 'r':
308 removed.append(abs)
308 removed.append(abs)
309 elif repo.dirstate[abs] == 'a':
309 elif repo.dirstate[abs] == 'a':
310 added.append(abs)
310 added.append(abs)
311 copies = {}
311 copies = {}
312 if similarity > 0:
312 if similarity > 0:
313 for old, new, score in similar.findrenames(repo,
313 for old, new, score in similar.findrenames(repo,
314 added + unknown, removed + deleted, similarity):
314 added + unknown, removed + deleted, similarity):
315 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
315 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
316 repo.ui.status(_('recording removal of %s as rename to %s '
316 repo.ui.status(_('recording removal of %s as rename to %s '
317 '(%d%% similar)\n') %
317 '(%d%% similar)\n') %
318 (m.rel(old), m.rel(new), score * 100))
318 (m.rel(old), m.rel(new), score * 100))
319 copies[new] = old
319 copies[new] = old
320
320
321 if not dry_run:
321 if not dry_run:
322 wctx = repo[None]
322 wctx = repo[None]
323 wlock = repo.wlock()
323 wlock = repo.wlock()
324 try:
324 try:
325 wctx.remove(deleted)
325 wctx.remove(deleted)
326 wctx.add(unknown)
326 wctx.add(unknown)
327 for new, old in copies.iteritems():
327 for new, old in copies.iteritems():
328 wctx.copy(old, new)
328 wctx.copy(old, new)
329 finally:
329 finally:
330 wlock.release()
330 wlock.release()
331
331
332 def copy(ui, repo, pats, opts, rename=False):
332 def copy(ui, repo, pats, opts, rename=False):
333 # called with the repo lock held
333 # called with the repo lock held
334 #
334 #
335 # hgsep => pathname that uses "/" to separate directories
335 # hgsep => pathname that uses "/" to separate directories
336 # ossep => pathname that uses os.sep to separate directories
336 # ossep => pathname that uses os.sep to separate directories
337 cwd = repo.getcwd()
337 cwd = repo.getcwd()
338 targets = {}
338 targets = {}
339 after = opts.get("after")
339 after = opts.get("after")
340 dryrun = opts.get("dry_run")
340 dryrun = opts.get("dry_run")
341 wctx = repo[None]
341 wctx = repo[None]
342
342
343 def walkpat(pat):
343 def walkpat(pat):
344 srcs = []
344 srcs = []
345 badstates = after and '?' or '?r'
345 badstates = after and '?' or '?r'
346 m = match(repo, [pat], opts, globbed=True)
346 m = match(repo, [pat], opts, globbed=True)
347 for abs in repo.walk(m):
347 for abs in repo.walk(m):
348 state = repo.dirstate[abs]
348 state = repo.dirstate[abs]
349 rel = m.rel(abs)
349 rel = m.rel(abs)
350 exact = m.exact(abs)
350 exact = m.exact(abs)
351 if state in badstates:
351 if state in badstates:
352 if exact and state == '?':
352 if exact and state == '?':
353 ui.warn(_('%s: not copying - file is not managed\n') % rel)
353 ui.warn(_('%s: not copying - file is not managed\n') % rel)
354 if exact and state == 'r':
354 if exact and state == 'r':
355 ui.warn(_('%s: not copying - file has been marked for'
355 ui.warn(_('%s: not copying - file has been marked for'
356 ' remove\n') % rel)
356 ' remove\n') % rel)
357 continue
357 continue
358 # abs: hgsep
358 # abs: hgsep
359 # rel: ossep
359 # rel: ossep
360 srcs.append((abs, rel, exact))
360 srcs.append((abs, rel, exact))
361 return srcs
361 return srcs
362
362
363 # abssrc: hgsep
363 # abssrc: hgsep
364 # relsrc: ossep
364 # relsrc: ossep
365 # otarget: ossep
365 # otarget: ossep
366 def copyfile(abssrc, relsrc, otarget, exact):
366 def copyfile(abssrc, relsrc, otarget, exact):
367 abstarget = util.canonpath(repo.root, cwd, otarget)
367 abstarget = util.canonpath(repo.root, cwd, otarget)
368 reltarget = repo.pathto(abstarget, cwd)
368 reltarget = repo.pathto(abstarget, cwd)
369 target = repo.wjoin(abstarget)
369 target = repo.wjoin(abstarget)
370 src = repo.wjoin(abssrc)
370 src = repo.wjoin(abssrc)
371 state = repo.dirstate[abstarget]
371 state = repo.dirstate[abstarget]
372
372
373 # check for collisions
373 # check for collisions
374 prevsrc = targets.get(abstarget)
374 prevsrc = targets.get(abstarget)
375 if prevsrc is not None:
375 if prevsrc is not None:
376 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
376 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
377 (reltarget, repo.pathto(abssrc, cwd),
377 (reltarget, repo.pathto(abssrc, cwd),
378 repo.pathto(prevsrc, cwd)))
378 repo.pathto(prevsrc, cwd)))
379 return
379 return
380
380
381 # check for overwrites
381 # check for overwrites
382 exists = os.path.exists(target)
382 exists = os.path.exists(target)
383 if not after and exists or after and state in 'mn':
383 if not after and exists or after and state in 'mn':
384 if not opts['force']:
384 if not opts['force']:
385 ui.warn(_('%s: not overwriting - file exists\n') %
385 ui.warn(_('%s: not overwriting - file exists\n') %
386 reltarget)
386 reltarget)
387 return
387 return
388
388
389 if after:
389 if after:
390 if not exists:
390 if not exists:
391 if rename:
391 if rename:
392 ui.warn(_('%s: not recording move - %s does not exist\n') %
392 ui.warn(_('%s: not recording move - %s does not exist\n') %
393 (relsrc, reltarget))
393 (relsrc, reltarget))
394 else:
394 else:
395 ui.warn(_('%s: not recording copy - %s does not exist\n') %
395 ui.warn(_('%s: not recording copy - %s does not exist\n') %
396 (relsrc, reltarget))
396 (relsrc, reltarget))
397 return
397 return
398 elif not dryrun:
398 elif not dryrun:
399 try:
399 try:
400 if exists:
400 if exists:
401 os.unlink(target)
401 os.unlink(target)
402 targetdir = os.path.dirname(target) or '.'
402 targetdir = os.path.dirname(target) or '.'
403 if not os.path.isdir(targetdir):
403 if not os.path.isdir(targetdir):
404 os.makedirs(targetdir)
404 os.makedirs(targetdir)
405 util.copyfile(src, target)
405 util.copyfile(src, target)
406 except IOError, inst:
406 except IOError, inst:
407 if inst.errno == errno.ENOENT:
407 if inst.errno == errno.ENOENT:
408 ui.warn(_('%s: deleted in working copy\n') % relsrc)
408 ui.warn(_('%s: deleted in working copy\n') % relsrc)
409 else:
409 else:
410 ui.warn(_('%s: cannot copy - %s\n') %
410 ui.warn(_('%s: cannot copy - %s\n') %
411 (relsrc, inst.strerror))
411 (relsrc, inst.strerror))
412 return True # report a failure
412 return True # report a failure
413
413
414 if ui.verbose or not exact:
414 if ui.verbose or not exact:
415 if rename:
415 if rename:
416 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
416 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
417 else:
417 else:
418 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
418 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
419
419
420 targets[abstarget] = abssrc
420 targets[abstarget] = abssrc
421
421
422 # fix up dirstate
422 # fix up dirstate
423 origsrc = repo.dirstate.copied(abssrc) or abssrc
423 origsrc = repo.dirstate.copied(abssrc) or abssrc
424 if abstarget == origsrc: # copying back a copy?
424 if abstarget == origsrc: # copying back a copy?
425 if state not in 'mn' and not dryrun:
425 if state not in 'mn' and not dryrun:
426 repo.dirstate.normallookup(abstarget)
426 repo.dirstate.normallookup(abstarget)
427 else:
427 else:
428 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
428 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
429 if not ui.quiet:
429 if not ui.quiet:
430 ui.warn(_("%s has not been committed yet, so no copy "
430 ui.warn(_("%s has not been committed yet, so no copy "
431 "data will be stored for %s.\n")
431 "data will be stored for %s.\n")
432 % (repo.pathto(origsrc, cwd), reltarget))
432 % (repo.pathto(origsrc, cwd), reltarget))
433 if repo.dirstate[abstarget] in '?r' and not dryrun:
433 if repo.dirstate[abstarget] in '?r' and not dryrun:
434 wctx.add([abstarget])
434 wctx.add([abstarget])
435 elif not dryrun:
435 elif not dryrun:
436 wctx.copy(origsrc, abstarget)
436 wctx.copy(origsrc, abstarget)
437
437
438 if rename and not dryrun:
438 if rename and not dryrun:
439 wctx.remove([abssrc], not after)
439 wctx.remove([abssrc], not after)
440
440
441 # pat: ossep
441 # pat: ossep
442 # dest ossep
442 # dest ossep
443 # srcs: list of (hgsep, hgsep, ossep, bool)
443 # srcs: list of (hgsep, hgsep, ossep, bool)
444 # return: function that takes hgsep and returns ossep
444 # return: function that takes hgsep and returns ossep
445 def targetpathfn(pat, dest, srcs):
445 def targetpathfn(pat, dest, srcs):
446 if os.path.isdir(pat):
446 if os.path.isdir(pat):
447 abspfx = util.canonpath(repo.root, cwd, pat)
447 abspfx = util.canonpath(repo.root, cwd, pat)
448 abspfx = util.localpath(abspfx)
448 abspfx = util.localpath(abspfx)
449 if destdirexists:
449 if destdirexists:
450 striplen = len(os.path.split(abspfx)[0])
450 striplen = len(os.path.split(abspfx)[0])
451 else:
451 else:
452 striplen = len(abspfx)
452 striplen = len(abspfx)
453 if striplen:
453 if striplen:
454 striplen += len(os.sep)
454 striplen += len(os.sep)
455 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
455 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
456 elif destdirexists:
456 elif destdirexists:
457 res = lambda p: os.path.join(dest,
457 res = lambda p: os.path.join(dest,
458 os.path.basename(util.localpath(p)))
458 os.path.basename(util.localpath(p)))
459 else:
459 else:
460 res = lambda p: dest
460 res = lambda p: dest
461 return res
461 return res
462
462
463 # pat: ossep
463 # pat: ossep
464 # dest ossep
464 # dest ossep
465 # srcs: list of (hgsep, hgsep, ossep, bool)
465 # srcs: list of (hgsep, hgsep, ossep, bool)
466 # return: function that takes hgsep and returns ossep
466 # return: function that takes hgsep and returns ossep
467 def targetpathafterfn(pat, dest, srcs):
467 def targetpathafterfn(pat, dest, srcs):
468 if matchmod.patkind(pat):
468 if matchmod.patkind(pat):
469 # a mercurial pattern
469 # a mercurial pattern
470 res = lambda p: os.path.join(dest,
470 res = lambda p: os.path.join(dest,
471 os.path.basename(util.localpath(p)))
471 os.path.basename(util.localpath(p)))
472 else:
472 else:
473 abspfx = util.canonpath(repo.root, cwd, pat)
473 abspfx = util.canonpath(repo.root, cwd, pat)
474 if len(abspfx) < len(srcs[0][0]):
474 if len(abspfx) < len(srcs[0][0]):
475 # A directory. Either the target path contains the last
475 # A directory. Either the target path contains the last
476 # component of the source path or it does not.
476 # component of the source path or it does not.
477 def evalpath(striplen):
477 def evalpath(striplen):
478 score = 0
478 score = 0
479 for s in srcs:
479 for s in srcs:
480 t = os.path.join(dest, util.localpath(s[0])[striplen:])
480 t = os.path.join(dest, util.localpath(s[0])[striplen:])
481 if os.path.exists(t):
481 if os.path.exists(t):
482 score += 1
482 score += 1
483 return score
483 return score
484
484
485 abspfx = util.localpath(abspfx)
485 abspfx = util.localpath(abspfx)
486 striplen = len(abspfx)
486 striplen = len(abspfx)
487 if striplen:
487 if striplen:
488 striplen += len(os.sep)
488 striplen += len(os.sep)
489 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
489 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
490 score = evalpath(striplen)
490 score = evalpath(striplen)
491 striplen1 = len(os.path.split(abspfx)[0])
491 striplen1 = len(os.path.split(abspfx)[0])
492 if striplen1:
492 if striplen1:
493 striplen1 += len(os.sep)
493 striplen1 += len(os.sep)
494 if evalpath(striplen1) > score:
494 if evalpath(striplen1) > score:
495 striplen = striplen1
495 striplen = striplen1
496 res = lambda p: os.path.join(dest,
496 res = lambda p: os.path.join(dest,
497 util.localpath(p)[striplen:])
497 util.localpath(p)[striplen:])
498 else:
498 else:
499 # a file
499 # a file
500 if destdirexists:
500 if destdirexists:
501 res = lambda p: os.path.join(dest,
501 res = lambda p: os.path.join(dest,
502 os.path.basename(util.localpath(p)))
502 os.path.basename(util.localpath(p)))
503 else:
503 else:
504 res = lambda p: dest
504 res = lambda p: dest
505 return res
505 return res
506
506
507
507
508 pats = expandpats(pats)
508 pats = expandpats(pats)
509 if not pats:
509 if not pats:
510 raise util.Abort(_('no source or destination specified'))
510 raise util.Abort(_('no source or destination specified'))
511 if len(pats) == 1:
511 if len(pats) == 1:
512 raise util.Abort(_('no destination specified'))
512 raise util.Abort(_('no destination specified'))
513 dest = pats.pop()
513 dest = pats.pop()
514 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
514 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
515 if not destdirexists:
515 if not destdirexists:
516 if len(pats) > 1 or matchmod.patkind(pats[0]):
516 if len(pats) > 1 or matchmod.patkind(pats[0]):
517 raise util.Abort(_('with multiple sources, destination must be an '
517 raise util.Abort(_('with multiple sources, destination must be an '
518 'existing directory'))
518 'existing directory'))
519 if util.endswithsep(dest):
519 if util.endswithsep(dest):
520 raise util.Abort(_('destination %s is not a directory') % dest)
520 raise util.Abort(_('destination %s is not a directory') % dest)
521
521
522 tfn = targetpathfn
522 tfn = targetpathfn
523 if after:
523 if after:
524 tfn = targetpathafterfn
524 tfn = targetpathafterfn
525 copylist = []
525 copylist = []
526 for pat in pats:
526 for pat in pats:
527 srcs = walkpat(pat)
527 srcs = walkpat(pat)
528 if not srcs:
528 if not srcs:
529 continue
529 continue
530 copylist.append((tfn(pat, dest, srcs), srcs))
530 copylist.append((tfn(pat, dest, srcs), srcs))
531 if not copylist:
531 if not copylist:
532 raise util.Abort(_('no files to copy'))
532 raise util.Abort(_('no files to copy'))
533
533
534 errors = 0
534 errors = 0
535 for targetpath, srcs in copylist:
535 for targetpath, srcs in copylist:
536 for abssrc, relsrc, exact in srcs:
536 for abssrc, relsrc, exact in srcs:
537 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
537 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
538 errors += 1
538 errors += 1
539
539
540 if errors:
540 if errors:
541 ui.warn(_('(consider using --after)\n'))
541 ui.warn(_('(consider using --after)\n'))
542
542
543 return errors != 0
543 return errors != 0
544
544
545 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
545 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
546 runargs=None, appendpid=False):
546 runargs=None, appendpid=False):
547 '''Run a command as a service.'''
547 '''Run a command as a service.'''
548
548
549 if opts['daemon'] and not opts['daemon_pipefds']:
549 if opts['daemon'] and not opts['daemon_pipefds']:
550 # Signal child process startup with file removal
550 # Signal child process startup with file removal
551 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
551 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
552 os.close(lockfd)
552 os.close(lockfd)
553 try:
553 try:
554 if not runargs:
554 if not runargs:
555 runargs = util.hgcmd() + sys.argv[1:]
555 runargs = util.hgcmd() + sys.argv[1:]
556 runargs.append('--daemon-pipefds=%s' % lockpath)
556 runargs.append('--daemon-pipefds=%s' % lockpath)
557 # Don't pass --cwd to the child process, because we've already
557 # Don't pass --cwd to the child process, because we've already
558 # changed directory.
558 # changed directory.
559 for i in xrange(1, len(runargs)):
559 for i in xrange(1, len(runargs)):
560 if runargs[i].startswith('--cwd='):
560 if runargs[i].startswith('--cwd='):
561 del runargs[i]
561 del runargs[i]
562 break
562 break
563 elif runargs[i].startswith('--cwd'):
563 elif runargs[i].startswith('--cwd'):
564 del runargs[i:i + 2]
564 del runargs[i:i + 2]
565 break
565 break
566 def condfn():
566 def condfn():
567 return not os.path.exists(lockpath)
567 return not os.path.exists(lockpath)
568 pid = util.rundetached(runargs, condfn)
568 pid = util.rundetached(runargs, condfn)
569 if pid < 0:
569 if pid < 0:
570 raise util.Abort(_('child process failed to start'))
570 raise util.Abort(_('child process failed to start'))
571 finally:
571 finally:
572 try:
572 try:
573 os.unlink(lockpath)
573 os.unlink(lockpath)
574 except OSError, e:
574 except OSError, e:
575 if e.errno != errno.ENOENT:
575 if e.errno != errno.ENOENT:
576 raise
576 raise
577 if parentfn:
577 if parentfn:
578 return parentfn(pid)
578 return parentfn(pid)
579 else:
579 else:
580 return
580 return
581
581
582 if initfn:
582 if initfn:
583 initfn()
583 initfn()
584
584
585 if opts['pid_file']:
585 if opts['pid_file']:
586 mode = appendpid and 'a' or 'w'
586 mode = appendpid and 'a' or 'w'
587 fp = open(opts['pid_file'], mode)
587 fp = open(opts['pid_file'], mode)
588 fp.write(str(os.getpid()) + '\n')
588 fp.write(str(os.getpid()) + '\n')
589 fp.close()
589 fp.close()
590
590
591 if opts['daemon_pipefds']:
591 if opts['daemon_pipefds']:
592 lockpath = opts['daemon_pipefds']
592 lockpath = opts['daemon_pipefds']
593 try:
593 try:
594 os.setsid()
594 os.setsid()
595 except AttributeError:
595 except AttributeError:
596 pass
596 pass
597 os.unlink(lockpath)
597 os.unlink(lockpath)
598 util.hidewindow()
598 util.hidewindow()
599 sys.stdout.flush()
599 sys.stdout.flush()
600 sys.stderr.flush()
600 sys.stderr.flush()
601
601
602 nullfd = os.open(util.nulldev, os.O_RDWR)
602 nullfd = os.open(util.nulldev, os.O_RDWR)
603 logfilefd = nullfd
603 logfilefd = nullfd
604 if logfile:
604 if logfile:
605 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
605 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
606 os.dup2(nullfd, 0)
606 os.dup2(nullfd, 0)
607 os.dup2(logfilefd, 1)
607 os.dup2(logfilefd, 1)
608 os.dup2(logfilefd, 2)
608 os.dup2(logfilefd, 2)
609 if nullfd not in (0, 1, 2):
609 if nullfd not in (0, 1, 2):
610 os.close(nullfd)
610 os.close(nullfd)
611 if logfile and logfilefd not in (0, 1, 2):
611 if logfile and logfilefd not in (0, 1, 2):
612 os.close(logfilefd)
612 os.close(logfilefd)
613
613
614 if runfn:
614 if runfn:
615 return runfn()
615 return runfn()
616
616
617 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
617 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
618 opts=None):
618 opts=None):
619 '''export changesets as hg patches.'''
619 '''export changesets as hg patches.'''
620
620
621 total = len(revs)
621 total = len(revs)
622 revwidth = max([len(str(rev)) for rev in revs])
622 revwidth = max([len(str(rev)) for rev in revs])
623
623
624 def single(rev, seqno, fp):
624 def single(rev, seqno, fp):
625 ctx = repo[rev]
625 ctx = repo[rev]
626 node = ctx.node()
626 node = ctx.node()
627 parents = [p.node() for p in ctx.parents() if p]
627 parents = [p.node() for p in ctx.parents() if p]
628 branch = ctx.branch()
628 branch = ctx.branch()
629 if switch_parent:
629 if switch_parent:
630 parents.reverse()
630 parents.reverse()
631 prev = (parents and parents[0]) or nullid
631 prev = (parents and parents[0]) or nullid
632
632
633 if not fp:
633 if not fp:
634 fp = make_file(repo, template, node, total=total, seqno=seqno,
634 fp = make_file(repo, template, node, total=total, seqno=seqno,
635 revwidth=revwidth, mode='ab')
635 revwidth=revwidth, mode='ab')
636 if fp != sys.stdout and hasattr(fp, 'name'):
636 if fp != sys.stdout and hasattr(fp, 'name'):
637 repo.ui.note("%s\n" % fp.name)
637 repo.ui.note("%s\n" % fp.name)
638
638
639 fp.write("# HG changeset patch\n")
639 fp.write("# HG changeset patch\n")
640 fp.write("# User %s\n" % ctx.user())
640 fp.write("# User %s\n" % ctx.user())
641 fp.write("# Date %d %d\n" % ctx.date())
641 fp.write("# Date %d %d\n" % ctx.date())
642 if branch and branch != 'default':
642 if branch and branch != 'default':
643 fp.write("# Branch %s\n" % branch)
643 fp.write("# Branch %s\n" % branch)
644 fp.write("# Node ID %s\n" % hex(node))
644 fp.write("# Node ID %s\n" % hex(node))
645 fp.write("# Parent %s\n" % hex(prev))
645 fp.write("# Parent %s\n" % hex(prev))
646 if len(parents) > 1:
646 if len(parents) > 1:
647 fp.write("# Parent %s\n" % hex(parents[1]))
647 fp.write("# Parent %s\n" % hex(parents[1]))
648 fp.write(ctx.description().rstrip())
648 fp.write(ctx.description().rstrip())
649 fp.write("\n\n")
649 fp.write("\n\n")
650
650
651 for chunk in patch.diff(repo, prev, node, opts=opts):
651 for chunk in patch.diff(repo, prev, node, opts=opts):
652 fp.write(chunk)
652 fp.write(chunk)
653
653
654 for seqno, rev in enumerate(revs):
654 for seqno, rev in enumerate(revs):
655 single(rev, seqno + 1, fp)
655 single(rev, seqno + 1, fp)
656
656
657 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
657 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
658 changes=None, stat=False, fp=None, prefix='',
658 changes=None, stat=False, fp=None, prefix='',
659 listsubrepos=False):
659 listsubrepos=False):
660 '''show diff or diffstat.'''
660 '''show diff or diffstat.'''
661 if fp is None:
661 if fp is None:
662 write = ui.write
662 write = ui.write
663 else:
663 else:
664 def write(s, **kw):
664 def write(s, **kw):
665 fp.write(s)
665 fp.write(s)
666
666
667 if stat:
667 if stat:
668 diffopts = diffopts.copy(context=0)
668 diffopts = diffopts.copy(context=0)
669 width = 80
669 width = 80
670 if not ui.plain():
670 if not ui.plain():
671 width = util.termwidth()
671 width = util.termwidth()
672 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
672 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
673 prefix=prefix)
673 prefix=prefix)
674 for chunk, label in patch.diffstatui(util.iterlines(chunks),
674 for chunk, label in patch.diffstatui(util.iterlines(chunks),
675 width=width,
675 width=width,
676 git=diffopts.git):
676 git=diffopts.git):
677 write(chunk, label=label)
677 write(chunk, label=label)
678 else:
678 else:
679 for chunk, label in patch.diffui(repo, node1, node2, match,
679 for chunk, label in patch.diffui(repo, node1, node2, match,
680 changes, diffopts, prefix=prefix):
680 changes, diffopts, prefix=prefix):
681 write(chunk, label=label)
681 write(chunk, label=label)
682
682
683 if listsubrepos:
683 if listsubrepos:
684 ctx1 = repo[node1]
684 ctx1 = repo[node1]
685 ctx2 = repo[node2]
685 ctx2 = repo[node2]
686 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
686 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
687 if node2 is not None:
687 if node2 is not None:
688 node2 = bin(ctx2.substate[subpath][1])
688 node2 = ctx2.substate[subpath][1]
689 submatch = matchmod.narrowmatcher(subpath, match)
689 submatch = matchmod.narrowmatcher(subpath, match)
690 sub.diff(diffopts, node2, submatch, changes=changes,
690 sub.diff(diffopts, node2, submatch, changes=changes,
691 stat=stat, fp=fp, prefix=prefix)
691 stat=stat, fp=fp, prefix=prefix)
692
692
693 class changeset_printer(object):
693 class changeset_printer(object):
694 '''show changeset information when templating not requested.'''
694 '''show changeset information when templating not requested.'''
695
695
696 def __init__(self, ui, repo, patch, diffopts, buffered):
696 def __init__(self, ui, repo, patch, diffopts, buffered):
697 self.ui = ui
697 self.ui = ui
698 self.repo = repo
698 self.repo = repo
699 self.buffered = buffered
699 self.buffered = buffered
700 self.patch = patch
700 self.patch = patch
701 self.diffopts = diffopts
701 self.diffopts = diffopts
702 self.header = {}
702 self.header = {}
703 self.hunk = {}
703 self.hunk = {}
704 self.lastheader = None
704 self.lastheader = None
705 self.footer = None
705 self.footer = None
706
706
707 def flush(self, rev):
707 def flush(self, rev):
708 if rev in self.header:
708 if rev in self.header:
709 h = self.header[rev]
709 h = self.header[rev]
710 if h != self.lastheader:
710 if h != self.lastheader:
711 self.lastheader = h
711 self.lastheader = h
712 self.ui.write(h)
712 self.ui.write(h)
713 del self.header[rev]
713 del self.header[rev]
714 if rev in self.hunk:
714 if rev in self.hunk:
715 self.ui.write(self.hunk[rev])
715 self.ui.write(self.hunk[rev])
716 del self.hunk[rev]
716 del self.hunk[rev]
717 return 1
717 return 1
718 return 0
718 return 0
719
719
720 def close(self):
720 def close(self):
721 if self.footer:
721 if self.footer:
722 self.ui.write(self.footer)
722 self.ui.write(self.footer)
723
723
724 def show(self, ctx, copies=None, matchfn=None, **props):
724 def show(self, ctx, copies=None, matchfn=None, **props):
725 if self.buffered:
725 if self.buffered:
726 self.ui.pushbuffer()
726 self.ui.pushbuffer()
727 self._show(ctx, copies, matchfn, props)
727 self._show(ctx, copies, matchfn, props)
728 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
728 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
729 else:
729 else:
730 self._show(ctx, copies, matchfn, props)
730 self._show(ctx, copies, matchfn, props)
731
731
732 def _show(self, ctx, copies, matchfn, props):
732 def _show(self, ctx, copies, matchfn, props):
733 '''show a single changeset or file revision'''
733 '''show a single changeset or file revision'''
734 changenode = ctx.node()
734 changenode = ctx.node()
735 rev = ctx.rev()
735 rev = ctx.rev()
736
736
737 if self.ui.quiet:
737 if self.ui.quiet:
738 self.ui.write("%d:%s\n" % (rev, short(changenode)),
738 self.ui.write("%d:%s\n" % (rev, short(changenode)),
739 label='log.node')
739 label='log.node')
740 return
740 return
741
741
742 log = self.repo.changelog
742 log = self.repo.changelog
743 date = util.datestr(ctx.date())
743 date = util.datestr(ctx.date())
744
744
745 hexfunc = self.ui.debugflag and hex or short
745 hexfunc = self.ui.debugflag and hex or short
746
746
747 parents = [(p, hexfunc(log.node(p)))
747 parents = [(p, hexfunc(log.node(p)))
748 for p in self._meaningful_parentrevs(log, rev)]
748 for p in self._meaningful_parentrevs(log, rev)]
749
749
750 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
750 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
751 label='log.changeset')
751 label='log.changeset')
752
752
753 branch = ctx.branch()
753 branch = ctx.branch()
754 # don't show the default branch name
754 # don't show the default branch name
755 if branch != 'default':
755 if branch != 'default':
756 branch = encoding.tolocal(branch)
756 branch = encoding.tolocal(branch)
757 self.ui.write(_("branch: %s\n") % branch,
757 self.ui.write(_("branch: %s\n") % branch,
758 label='log.branch')
758 label='log.branch')
759 for tag in self.repo.nodetags(changenode):
759 for tag in self.repo.nodetags(changenode):
760 self.ui.write(_("tag: %s\n") % tag,
760 self.ui.write(_("tag: %s\n") % tag,
761 label='log.tag')
761 label='log.tag')
762 for parent in parents:
762 for parent in parents:
763 self.ui.write(_("parent: %d:%s\n") % parent,
763 self.ui.write(_("parent: %d:%s\n") % parent,
764 label='log.parent')
764 label='log.parent')
765
765
766 if self.ui.debugflag:
766 if self.ui.debugflag:
767 mnode = ctx.manifestnode()
767 mnode = ctx.manifestnode()
768 self.ui.write(_("manifest: %d:%s\n") %
768 self.ui.write(_("manifest: %d:%s\n") %
769 (self.repo.manifest.rev(mnode), hex(mnode)),
769 (self.repo.manifest.rev(mnode), hex(mnode)),
770 label='ui.debug log.manifest')
770 label='ui.debug log.manifest')
771 self.ui.write(_("user: %s\n") % ctx.user(),
771 self.ui.write(_("user: %s\n") % ctx.user(),
772 label='log.user')
772 label='log.user')
773 self.ui.write(_("date: %s\n") % date,
773 self.ui.write(_("date: %s\n") % date,
774 label='log.date')
774 label='log.date')
775
775
776 if self.ui.debugflag:
776 if self.ui.debugflag:
777 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
777 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
778 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
778 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
779 files):
779 files):
780 if value:
780 if value:
781 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
781 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
782 label='ui.debug log.files')
782 label='ui.debug log.files')
783 elif ctx.files() and self.ui.verbose:
783 elif ctx.files() and self.ui.verbose:
784 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
784 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
785 label='ui.note log.files')
785 label='ui.note log.files')
786 if copies and self.ui.verbose:
786 if copies and self.ui.verbose:
787 copies = ['%s (%s)' % c for c in copies]
787 copies = ['%s (%s)' % c for c in copies]
788 self.ui.write(_("copies: %s\n") % ' '.join(copies),
788 self.ui.write(_("copies: %s\n") % ' '.join(copies),
789 label='ui.note log.copies')
789 label='ui.note log.copies')
790
790
791 extra = ctx.extra()
791 extra = ctx.extra()
792 if extra and self.ui.debugflag:
792 if extra and self.ui.debugflag:
793 for key, value in sorted(extra.items()):
793 for key, value in sorted(extra.items()):
794 self.ui.write(_("extra: %s=%s\n")
794 self.ui.write(_("extra: %s=%s\n")
795 % (key, value.encode('string_escape')),
795 % (key, value.encode('string_escape')),
796 label='ui.debug log.extra')
796 label='ui.debug log.extra')
797
797
798 description = ctx.description().strip()
798 description = ctx.description().strip()
799 if description:
799 if description:
800 if self.ui.verbose:
800 if self.ui.verbose:
801 self.ui.write(_("description:\n"),
801 self.ui.write(_("description:\n"),
802 label='ui.note log.description')
802 label='ui.note log.description')
803 self.ui.write(description,
803 self.ui.write(description,
804 label='ui.note log.description')
804 label='ui.note log.description')
805 self.ui.write("\n\n")
805 self.ui.write("\n\n")
806 else:
806 else:
807 self.ui.write(_("summary: %s\n") %
807 self.ui.write(_("summary: %s\n") %
808 description.splitlines()[0],
808 description.splitlines()[0],
809 label='log.summary')
809 label='log.summary')
810 self.ui.write("\n")
810 self.ui.write("\n")
811
811
812 self.showpatch(changenode, matchfn)
812 self.showpatch(changenode, matchfn)
813
813
814 def showpatch(self, node, matchfn):
814 def showpatch(self, node, matchfn):
815 if not matchfn:
815 if not matchfn:
816 matchfn = self.patch
816 matchfn = self.patch
817 if matchfn:
817 if matchfn:
818 stat = self.diffopts.get('stat')
818 stat = self.diffopts.get('stat')
819 diff = self.diffopts.get('patch')
819 diff = self.diffopts.get('patch')
820 diffopts = patch.diffopts(self.ui, self.diffopts)
820 diffopts = patch.diffopts(self.ui, self.diffopts)
821 prev = self.repo.changelog.parents(node)[0]
821 prev = self.repo.changelog.parents(node)[0]
822 if stat:
822 if stat:
823 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
823 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
824 match=matchfn, stat=True)
824 match=matchfn, stat=True)
825 if diff:
825 if diff:
826 if stat:
826 if stat:
827 self.ui.write("\n")
827 self.ui.write("\n")
828 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
828 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
829 match=matchfn, stat=False)
829 match=matchfn, stat=False)
830 self.ui.write("\n")
830 self.ui.write("\n")
831
831
832 def _meaningful_parentrevs(self, log, rev):
832 def _meaningful_parentrevs(self, log, rev):
833 """Return list of meaningful (or all if debug) parentrevs for rev.
833 """Return list of meaningful (or all if debug) parentrevs for rev.
834
834
835 For merges (two non-nullrev revisions) both parents are meaningful.
835 For merges (two non-nullrev revisions) both parents are meaningful.
836 Otherwise the first parent revision is considered meaningful if it
836 Otherwise the first parent revision is considered meaningful if it
837 is not the preceding revision.
837 is not the preceding revision.
838 """
838 """
839 parents = log.parentrevs(rev)
839 parents = log.parentrevs(rev)
840 if not self.ui.debugflag and parents[1] == nullrev:
840 if not self.ui.debugflag and parents[1] == nullrev:
841 if parents[0] >= rev - 1:
841 if parents[0] >= rev - 1:
842 parents = []
842 parents = []
843 else:
843 else:
844 parents = [parents[0]]
844 parents = [parents[0]]
845 return parents
845 return parents
846
846
847
847
848 class changeset_templater(changeset_printer):
848 class changeset_templater(changeset_printer):
849 '''format changeset information.'''
849 '''format changeset information.'''
850
850
851 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
851 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
852 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
852 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
853 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
853 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
854 defaulttempl = {
854 defaulttempl = {
855 'parent': '{rev}:{node|formatnode} ',
855 'parent': '{rev}:{node|formatnode} ',
856 'manifest': '{rev}:{node|formatnode}',
856 'manifest': '{rev}:{node|formatnode}',
857 'file_copy': '{name} ({source})',
857 'file_copy': '{name} ({source})',
858 'extra': '{key}={value|stringescape}'
858 'extra': '{key}={value|stringescape}'
859 }
859 }
860 # filecopy is preserved for compatibility reasons
860 # filecopy is preserved for compatibility reasons
861 defaulttempl['filecopy'] = defaulttempl['file_copy']
861 defaulttempl['filecopy'] = defaulttempl['file_copy']
862 self.t = templater.templater(mapfile, {'formatnode': formatnode},
862 self.t = templater.templater(mapfile, {'formatnode': formatnode},
863 cache=defaulttempl)
863 cache=defaulttempl)
864 self.cache = {}
864 self.cache = {}
865
865
866 def use_template(self, t):
866 def use_template(self, t):
867 '''set template string to use'''
867 '''set template string to use'''
868 self.t.cache['changeset'] = t
868 self.t.cache['changeset'] = t
869
869
870 def _meaningful_parentrevs(self, ctx):
870 def _meaningful_parentrevs(self, ctx):
871 """Return list of meaningful (or all if debug) parentrevs for rev.
871 """Return list of meaningful (or all if debug) parentrevs for rev.
872 """
872 """
873 parents = ctx.parents()
873 parents = ctx.parents()
874 if len(parents) > 1:
874 if len(parents) > 1:
875 return parents
875 return parents
876 if self.ui.debugflag:
876 if self.ui.debugflag:
877 return [parents[0], self.repo['null']]
877 return [parents[0], self.repo['null']]
878 if parents[0].rev() >= ctx.rev() - 1:
878 if parents[0].rev() >= ctx.rev() - 1:
879 return []
879 return []
880 return parents
880 return parents
881
881
882 def _show(self, ctx, copies, matchfn, props):
882 def _show(self, ctx, copies, matchfn, props):
883 '''show a single changeset or file revision'''
883 '''show a single changeset or file revision'''
884
884
885 showlist = templatekw.showlist
885 showlist = templatekw.showlist
886
886
887 # showparents() behaviour depends on ui trace level which
887 # showparents() behaviour depends on ui trace level which
888 # causes unexpected behaviours at templating level and makes
888 # causes unexpected behaviours at templating level and makes
889 # it harder to extract it in a standalone function. Its
889 # it harder to extract it in a standalone function. Its
890 # behaviour cannot be changed so leave it here for now.
890 # behaviour cannot be changed so leave it here for now.
891 def showparents(**args):
891 def showparents(**args):
892 ctx = args['ctx']
892 ctx = args['ctx']
893 parents = [[('rev', p.rev()), ('node', p.hex())]
893 parents = [[('rev', p.rev()), ('node', p.hex())]
894 for p in self._meaningful_parentrevs(ctx)]
894 for p in self._meaningful_parentrevs(ctx)]
895 return showlist('parent', parents, **args)
895 return showlist('parent', parents, **args)
896
896
897 props = props.copy()
897 props = props.copy()
898 props.update(templatekw.keywords)
898 props.update(templatekw.keywords)
899 props['parents'] = showparents
899 props['parents'] = showparents
900 props['templ'] = self.t
900 props['templ'] = self.t
901 props['ctx'] = ctx
901 props['ctx'] = ctx
902 props['repo'] = self.repo
902 props['repo'] = self.repo
903 props['revcache'] = {'copies': copies}
903 props['revcache'] = {'copies': copies}
904 props['cache'] = self.cache
904 props['cache'] = self.cache
905
905
906 # find correct templates for current mode
906 # find correct templates for current mode
907
907
908 tmplmodes = [
908 tmplmodes = [
909 (True, None),
909 (True, None),
910 (self.ui.verbose, 'verbose'),
910 (self.ui.verbose, 'verbose'),
911 (self.ui.quiet, 'quiet'),
911 (self.ui.quiet, 'quiet'),
912 (self.ui.debugflag, 'debug'),
912 (self.ui.debugflag, 'debug'),
913 ]
913 ]
914
914
915 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
915 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
916 for mode, postfix in tmplmodes:
916 for mode, postfix in tmplmodes:
917 for type in types:
917 for type in types:
918 cur = postfix and ('%s_%s' % (type, postfix)) or type
918 cur = postfix and ('%s_%s' % (type, postfix)) or type
919 if mode and cur in self.t:
919 if mode and cur in self.t:
920 types[type] = cur
920 types[type] = cur
921
921
922 try:
922 try:
923
923
924 # write header
924 # write header
925 if types['header']:
925 if types['header']:
926 h = templater.stringify(self.t(types['header'], **props))
926 h = templater.stringify(self.t(types['header'], **props))
927 if self.buffered:
927 if self.buffered:
928 self.header[ctx.rev()] = h
928 self.header[ctx.rev()] = h
929 else:
929 else:
930 if self.lastheader != h:
930 if self.lastheader != h:
931 self.lastheader = h
931 self.lastheader = h
932 self.ui.write(h)
932 self.ui.write(h)
933
933
934 # write changeset metadata, then patch if requested
934 # write changeset metadata, then patch if requested
935 key = types['changeset']
935 key = types['changeset']
936 self.ui.write(templater.stringify(self.t(key, **props)))
936 self.ui.write(templater.stringify(self.t(key, **props)))
937 self.showpatch(ctx.node(), matchfn)
937 self.showpatch(ctx.node(), matchfn)
938
938
939 if types['footer']:
939 if types['footer']:
940 if not self.footer:
940 if not self.footer:
941 self.footer = templater.stringify(self.t(types['footer'],
941 self.footer = templater.stringify(self.t(types['footer'],
942 **props))
942 **props))
943
943
944 except KeyError, inst:
944 except KeyError, inst:
945 msg = _("%s: no key named '%s'")
945 msg = _("%s: no key named '%s'")
946 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
946 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
947 except SyntaxError, inst:
947 except SyntaxError, inst:
948 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
948 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
949
949
950 def show_changeset(ui, repo, opts, buffered=False):
950 def show_changeset(ui, repo, opts, buffered=False):
951 """show one changeset using template or regular display.
951 """show one changeset using template or regular display.
952
952
953 Display format will be the first non-empty hit of:
953 Display format will be the first non-empty hit of:
954 1. option 'template'
954 1. option 'template'
955 2. option 'style'
955 2. option 'style'
956 3. [ui] setting 'logtemplate'
956 3. [ui] setting 'logtemplate'
957 4. [ui] setting 'style'
957 4. [ui] setting 'style'
958 If all of these values are either the unset or the empty string,
958 If all of these values are either the unset or the empty string,
959 regular display via changeset_printer() is done.
959 regular display via changeset_printer() is done.
960 """
960 """
961 # options
961 # options
962 patch = False
962 patch = False
963 if opts.get('patch') or opts.get('stat'):
963 if opts.get('patch') or opts.get('stat'):
964 patch = matchall(repo)
964 patch = matchall(repo)
965
965
966 tmpl = opts.get('template')
966 tmpl = opts.get('template')
967 style = None
967 style = None
968 if tmpl:
968 if tmpl:
969 tmpl = templater.parsestring(tmpl, quoted=False)
969 tmpl = templater.parsestring(tmpl, quoted=False)
970 else:
970 else:
971 style = opts.get('style')
971 style = opts.get('style')
972
972
973 # ui settings
973 # ui settings
974 if not (tmpl or style):
974 if not (tmpl or style):
975 tmpl = ui.config('ui', 'logtemplate')
975 tmpl = ui.config('ui', 'logtemplate')
976 if tmpl:
976 if tmpl:
977 tmpl = templater.parsestring(tmpl)
977 tmpl = templater.parsestring(tmpl)
978 else:
978 else:
979 style = util.expandpath(ui.config('ui', 'style', ''))
979 style = util.expandpath(ui.config('ui', 'style', ''))
980
980
981 if not (tmpl or style):
981 if not (tmpl or style):
982 return changeset_printer(ui, repo, patch, opts, buffered)
982 return changeset_printer(ui, repo, patch, opts, buffered)
983
983
984 mapfile = None
984 mapfile = None
985 if style and not tmpl:
985 if style and not tmpl:
986 mapfile = style
986 mapfile = style
987 if not os.path.split(mapfile)[0]:
987 if not os.path.split(mapfile)[0]:
988 mapname = (templater.templatepath('map-cmdline.' + mapfile)
988 mapname = (templater.templatepath('map-cmdline.' + mapfile)
989 or templater.templatepath(mapfile))
989 or templater.templatepath(mapfile))
990 if mapname:
990 if mapname:
991 mapfile = mapname
991 mapfile = mapname
992
992
993 try:
993 try:
994 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
994 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
995 except SyntaxError, inst:
995 except SyntaxError, inst:
996 raise util.Abort(inst.args[0])
996 raise util.Abort(inst.args[0])
997 if tmpl:
997 if tmpl:
998 t.use_template(tmpl)
998 t.use_template(tmpl)
999 return t
999 return t
1000
1000
1001 def finddate(ui, repo, date):
1001 def finddate(ui, repo, date):
1002 """Find the tipmost changeset that matches the given date spec"""
1002 """Find the tipmost changeset that matches the given date spec"""
1003
1003
1004 df = util.matchdate(date)
1004 df = util.matchdate(date)
1005 m = matchall(repo)
1005 m = matchall(repo)
1006 results = {}
1006 results = {}
1007
1007
1008 def prep(ctx, fns):
1008 def prep(ctx, fns):
1009 d = ctx.date()
1009 d = ctx.date()
1010 if df(d[0]):
1010 if df(d[0]):
1011 results[ctx.rev()] = d
1011 results[ctx.rev()] = d
1012
1012
1013 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1013 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1014 rev = ctx.rev()
1014 rev = ctx.rev()
1015 if rev in results:
1015 if rev in results:
1016 ui.status(_("Found revision %s from %s\n") %
1016 ui.status(_("Found revision %s from %s\n") %
1017 (rev, util.datestr(results[rev])))
1017 (rev, util.datestr(results[rev])))
1018 return str(rev)
1018 return str(rev)
1019
1019
1020 raise util.Abort(_("revision matching date not found"))
1020 raise util.Abort(_("revision matching date not found"))
1021
1021
1022 def walkchangerevs(repo, match, opts, prepare):
1022 def walkchangerevs(repo, match, opts, prepare):
1023 '''Iterate over files and the revs in which they changed.
1023 '''Iterate over files and the revs in which they changed.
1024
1024
1025 Callers most commonly need to iterate backwards over the history
1025 Callers most commonly need to iterate backwards over the history
1026 in which they are interested. Doing so has awful (quadratic-looking)
1026 in which they are interested. Doing so has awful (quadratic-looking)
1027 performance, so we use iterators in a "windowed" way.
1027 performance, so we use iterators in a "windowed" way.
1028
1028
1029 We walk a window of revisions in the desired order. Within the
1029 We walk a window of revisions in the desired order. Within the
1030 window, we first walk forwards to gather data, then in the desired
1030 window, we first walk forwards to gather data, then in the desired
1031 order (usually backwards) to display it.
1031 order (usually backwards) to display it.
1032
1032
1033 This function returns an iterator yielding contexts. Before
1033 This function returns an iterator yielding contexts. Before
1034 yielding each context, the iterator will first call the prepare
1034 yielding each context, the iterator will first call the prepare
1035 function on each context in the window in forward order.'''
1035 function on each context in the window in forward order.'''
1036
1036
1037 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1037 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1038 if start < end:
1038 if start < end:
1039 while start < end:
1039 while start < end:
1040 yield start, min(windowsize, end - start)
1040 yield start, min(windowsize, end - start)
1041 start += windowsize
1041 start += windowsize
1042 if windowsize < sizelimit:
1042 if windowsize < sizelimit:
1043 windowsize *= 2
1043 windowsize *= 2
1044 else:
1044 else:
1045 while start > end:
1045 while start > end:
1046 yield start, min(windowsize, start - end - 1)
1046 yield start, min(windowsize, start - end - 1)
1047 start -= windowsize
1047 start -= windowsize
1048 if windowsize < sizelimit:
1048 if windowsize < sizelimit:
1049 windowsize *= 2
1049 windowsize *= 2
1050
1050
1051 follow = opts.get('follow') or opts.get('follow_first')
1051 follow = opts.get('follow') or opts.get('follow_first')
1052
1052
1053 if not len(repo):
1053 if not len(repo):
1054 return []
1054 return []
1055
1055
1056 if follow:
1056 if follow:
1057 defrange = '%s:0' % repo['.'].rev()
1057 defrange = '%s:0' % repo['.'].rev()
1058 else:
1058 else:
1059 defrange = '-1:0'
1059 defrange = '-1:0'
1060 revs = revrange(repo, opts['rev'] or [defrange])
1060 revs = revrange(repo, opts['rev'] or [defrange])
1061 if not revs:
1061 if not revs:
1062 return []
1062 return []
1063 wanted = set()
1063 wanted = set()
1064 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1064 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1065 fncache = {}
1065 fncache = {}
1066 change = util.cachefunc(repo.changectx)
1066 change = util.cachefunc(repo.changectx)
1067
1067
1068 # First step is to fill wanted, the set of revisions that we want to yield.
1068 # First step is to fill wanted, the set of revisions that we want to yield.
1069 # When it does not induce extra cost, we also fill fncache for revisions in
1069 # When it does not induce extra cost, we also fill fncache for revisions in
1070 # wanted: a cache of filenames that were changed (ctx.files()) and that
1070 # wanted: a cache of filenames that were changed (ctx.files()) and that
1071 # match the file filtering conditions.
1071 # match the file filtering conditions.
1072
1072
1073 if not slowpath and not match.files():
1073 if not slowpath and not match.files():
1074 # No files, no patterns. Display all revs.
1074 # No files, no patterns. Display all revs.
1075 wanted = set(revs)
1075 wanted = set(revs)
1076 copies = []
1076 copies = []
1077
1077
1078 if not slowpath:
1078 if not slowpath:
1079 # We only have to read through the filelog to find wanted revisions
1079 # We only have to read through the filelog to find wanted revisions
1080
1080
1081 minrev, maxrev = min(revs), max(revs)
1081 minrev, maxrev = min(revs), max(revs)
1082 def filerevgen(filelog, last):
1082 def filerevgen(filelog, last):
1083 """
1083 """
1084 Only files, no patterns. Check the history of each file.
1084 Only files, no patterns. Check the history of each file.
1085
1085
1086 Examines filelog entries within minrev, maxrev linkrev range
1086 Examines filelog entries within minrev, maxrev linkrev range
1087 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1087 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1088 tuples in backwards order
1088 tuples in backwards order
1089 """
1089 """
1090 cl_count = len(repo)
1090 cl_count = len(repo)
1091 revs = []
1091 revs = []
1092 for j in xrange(0, last + 1):
1092 for j in xrange(0, last + 1):
1093 linkrev = filelog.linkrev(j)
1093 linkrev = filelog.linkrev(j)
1094 if linkrev < minrev:
1094 if linkrev < minrev:
1095 continue
1095 continue
1096 # only yield rev for which we have the changelog, it can
1096 # only yield rev for which we have the changelog, it can
1097 # happen while doing "hg log" during a pull or commit
1097 # happen while doing "hg log" during a pull or commit
1098 if linkrev > maxrev or linkrev >= cl_count:
1098 if linkrev > maxrev or linkrev >= cl_count:
1099 break
1099 break
1100
1100
1101 parentlinkrevs = []
1101 parentlinkrevs = []
1102 for p in filelog.parentrevs(j):
1102 for p in filelog.parentrevs(j):
1103 if p != nullrev:
1103 if p != nullrev:
1104 parentlinkrevs.append(filelog.linkrev(p))
1104 parentlinkrevs.append(filelog.linkrev(p))
1105 n = filelog.node(j)
1105 n = filelog.node(j)
1106 revs.append((linkrev, parentlinkrevs,
1106 revs.append((linkrev, parentlinkrevs,
1107 follow and filelog.renamed(n)))
1107 follow and filelog.renamed(n)))
1108
1108
1109 return reversed(revs)
1109 return reversed(revs)
1110 def iterfiles():
1110 def iterfiles():
1111 for filename in match.files():
1111 for filename in match.files():
1112 yield filename, None
1112 yield filename, None
1113 for filename_node in copies:
1113 for filename_node in copies:
1114 yield filename_node
1114 yield filename_node
1115 for file_, node in iterfiles():
1115 for file_, node in iterfiles():
1116 filelog = repo.file(file_)
1116 filelog = repo.file(file_)
1117 if not len(filelog):
1117 if not len(filelog):
1118 if node is None:
1118 if node is None:
1119 # A zero count may be a directory or deleted file, so
1119 # A zero count may be a directory or deleted file, so
1120 # try to find matching entries on the slow path.
1120 # try to find matching entries on the slow path.
1121 if follow:
1121 if follow:
1122 raise util.Abort(
1122 raise util.Abort(
1123 _('cannot follow nonexistent file: "%s"') % file_)
1123 _('cannot follow nonexistent file: "%s"') % file_)
1124 slowpath = True
1124 slowpath = True
1125 break
1125 break
1126 else:
1126 else:
1127 continue
1127 continue
1128
1128
1129 if node is None:
1129 if node is None:
1130 last = len(filelog) - 1
1130 last = len(filelog) - 1
1131 else:
1131 else:
1132 last = filelog.rev(node)
1132 last = filelog.rev(node)
1133
1133
1134
1134
1135 # keep track of all ancestors of the file
1135 # keep track of all ancestors of the file
1136 ancestors = set([filelog.linkrev(last)])
1136 ancestors = set([filelog.linkrev(last)])
1137
1137
1138 # iterate from latest to oldest revision
1138 # iterate from latest to oldest revision
1139 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1139 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1140 if rev not in ancestors:
1140 if rev not in ancestors:
1141 continue
1141 continue
1142 # XXX insert 1327 fix here
1142 # XXX insert 1327 fix here
1143 if flparentlinkrevs:
1143 if flparentlinkrevs:
1144 ancestors.update(flparentlinkrevs)
1144 ancestors.update(flparentlinkrevs)
1145
1145
1146 fncache.setdefault(rev, []).append(file_)
1146 fncache.setdefault(rev, []).append(file_)
1147 wanted.add(rev)
1147 wanted.add(rev)
1148 if copied:
1148 if copied:
1149 copies.append(copied)
1149 copies.append(copied)
1150 if slowpath:
1150 if slowpath:
1151 # We have to read the changelog to match filenames against
1151 # We have to read the changelog to match filenames against
1152 # changed files
1152 # changed files
1153
1153
1154 if follow:
1154 if follow:
1155 raise util.Abort(_('can only follow copies/renames for explicit '
1155 raise util.Abort(_('can only follow copies/renames for explicit '
1156 'filenames'))
1156 'filenames'))
1157
1157
1158 # The slow path checks files modified in every changeset.
1158 # The slow path checks files modified in every changeset.
1159 for i in sorted(revs):
1159 for i in sorted(revs):
1160 ctx = change(i)
1160 ctx = change(i)
1161 matches = filter(match, ctx.files())
1161 matches = filter(match, ctx.files())
1162 if matches:
1162 if matches:
1163 fncache[i] = matches
1163 fncache[i] = matches
1164 wanted.add(i)
1164 wanted.add(i)
1165
1165
1166 class followfilter(object):
1166 class followfilter(object):
1167 def __init__(self, onlyfirst=False):
1167 def __init__(self, onlyfirst=False):
1168 self.startrev = nullrev
1168 self.startrev = nullrev
1169 self.roots = set()
1169 self.roots = set()
1170 self.onlyfirst = onlyfirst
1170 self.onlyfirst = onlyfirst
1171
1171
1172 def match(self, rev):
1172 def match(self, rev):
1173 def realparents(rev):
1173 def realparents(rev):
1174 if self.onlyfirst:
1174 if self.onlyfirst:
1175 return repo.changelog.parentrevs(rev)[0:1]
1175 return repo.changelog.parentrevs(rev)[0:1]
1176 else:
1176 else:
1177 return filter(lambda x: x != nullrev,
1177 return filter(lambda x: x != nullrev,
1178 repo.changelog.parentrevs(rev))
1178 repo.changelog.parentrevs(rev))
1179
1179
1180 if self.startrev == nullrev:
1180 if self.startrev == nullrev:
1181 self.startrev = rev
1181 self.startrev = rev
1182 return True
1182 return True
1183
1183
1184 if rev > self.startrev:
1184 if rev > self.startrev:
1185 # forward: all descendants
1185 # forward: all descendants
1186 if not self.roots:
1186 if not self.roots:
1187 self.roots.add(self.startrev)
1187 self.roots.add(self.startrev)
1188 for parent in realparents(rev):
1188 for parent in realparents(rev):
1189 if parent in self.roots:
1189 if parent in self.roots:
1190 self.roots.add(rev)
1190 self.roots.add(rev)
1191 return True
1191 return True
1192 else:
1192 else:
1193 # backwards: all parents
1193 # backwards: all parents
1194 if not self.roots:
1194 if not self.roots:
1195 self.roots.update(realparents(self.startrev))
1195 self.roots.update(realparents(self.startrev))
1196 if rev in self.roots:
1196 if rev in self.roots:
1197 self.roots.remove(rev)
1197 self.roots.remove(rev)
1198 self.roots.update(realparents(rev))
1198 self.roots.update(realparents(rev))
1199 return True
1199 return True
1200
1200
1201 return False
1201 return False
1202
1202
1203 # it might be worthwhile to do this in the iterator if the rev range
1203 # it might be worthwhile to do this in the iterator if the rev range
1204 # is descending and the prune args are all within that range
1204 # is descending and the prune args are all within that range
1205 for rev in opts.get('prune', ()):
1205 for rev in opts.get('prune', ()):
1206 rev = repo.changelog.rev(repo.lookup(rev))
1206 rev = repo.changelog.rev(repo.lookup(rev))
1207 ff = followfilter()
1207 ff = followfilter()
1208 stop = min(revs[0], revs[-1])
1208 stop = min(revs[0], revs[-1])
1209 for x in xrange(rev, stop - 1, -1):
1209 for x in xrange(rev, stop - 1, -1):
1210 if ff.match(x):
1210 if ff.match(x):
1211 wanted.discard(x)
1211 wanted.discard(x)
1212
1212
1213 # Now that wanted is correctly initialized, we can iterate over the
1213 # Now that wanted is correctly initialized, we can iterate over the
1214 # revision range, yielding only revisions in wanted.
1214 # revision range, yielding only revisions in wanted.
1215 def iterate():
1215 def iterate():
1216 if follow and not match.files():
1216 if follow and not match.files():
1217 ff = followfilter(onlyfirst=opts.get('follow_first'))
1217 ff = followfilter(onlyfirst=opts.get('follow_first'))
1218 def want(rev):
1218 def want(rev):
1219 return ff.match(rev) and rev in wanted
1219 return ff.match(rev) and rev in wanted
1220 else:
1220 else:
1221 def want(rev):
1221 def want(rev):
1222 return rev in wanted
1222 return rev in wanted
1223
1223
1224 for i, window in increasing_windows(0, len(revs)):
1224 for i, window in increasing_windows(0, len(revs)):
1225 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1225 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1226 for rev in sorted(nrevs):
1226 for rev in sorted(nrevs):
1227 fns = fncache.get(rev)
1227 fns = fncache.get(rev)
1228 ctx = change(rev)
1228 ctx = change(rev)
1229 if not fns:
1229 if not fns:
1230 def fns_generator():
1230 def fns_generator():
1231 for f in ctx.files():
1231 for f in ctx.files():
1232 if match(f):
1232 if match(f):
1233 yield f
1233 yield f
1234 fns = fns_generator()
1234 fns = fns_generator()
1235 prepare(ctx, fns)
1235 prepare(ctx, fns)
1236 for rev in nrevs:
1236 for rev in nrevs:
1237 yield change(rev)
1237 yield change(rev)
1238 return iterate()
1238 return iterate()
1239
1239
1240 def commit(ui, repo, commitfunc, pats, opts):
1240 def commit(ui, repo, commitfunc, pats, opts):
1241 '''commit the specified files or all outstanding changes'''
1241 '''commit the specified files or all outstanding changes'''
1242 date = opts.get('date')
1242 date = opts.get('date')
1243 if date:
1243 if date:
1244 opts['date'] = util.parsedate(date)
1244 opts['date'] = util.parsedate(date)
1245 message = logmessage(opts)
1245 message = logmessage(opts)
1246
1246
1247 # extract addremove carefully -- this function can be called from a command
1247 # extract addremove carefully -- this function can be called from a command
1248 # that doesn't support addremove
1248 # that doesn't support addremove
1249 if opts.get('addremove'):
1249 if opts.get('addremove'):
1250 addremove(repo, pats, opts)
1250 addremove(repo, pats, opts)
1251
1251
1252 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1252 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1253
1253
1254 def commiteditor(repo, ctx, subs):
1254 def commiteditor(repo, ctx, subs):
1255 if ctx.description():
1255 if ctx.description():
1256 return ctx.description()
1256 return ctx.description()
1257 return commitforceeditor(repo, ctx, subs)
1257 return commitforceeditor(repo, ctx, subs)
1258
1258
1259 def commitforceeditor(repo, ctx, subs):
1259 def commitforceeditor(repo, ctx, subs):
1260 edittext = []
1260 edittext = []
1261 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1261 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1262 if ctx.description():
1262 if ctx.description():
1263 edittext.append(ctx.description())
1263 edittext.append(ctx.description())
1264 edittext.append("")
1264 edittext.append("")
1265 edittext.append("") # Empty line between message and comments.
1265 edittext.append("") # Empty line between message and comments.
1266 edittext.append(_("HG: Enter commit message."
1266 edittext.append(_("HG: Enter commit message."
1267 " Lines beginning with 'HG:' are removed."))
1267 " Lines beginning with 'HG:' are removed."))
1268 edittext.append(_("HG: Leave message empty to abort commit."))
1268 edittext.append(_("HG: Leave message empty to abort commit."))
1269 edittext.append("HG: --")
1269 edittext.append("HG: --")
1270 edittext.append(_("HG: user: %s") % ctx.user())
1270 edittext.append(_("HG: user: %s") % ctx.user())
1271 if ctx.p2():
1271 if ctx.p2():
1272 edittext.append(_("HG: branch merge"))
1272 edittext.append(_("HG: branch merge"))
1273 if ctx.branch():
1273 if ctx.branch():
1274 edittext.append(_("HG: branch '%s'")
1274 edittext.append(_("HG: branch '%s'")
1275 % encoding.tolocal(ctx.branch()))
1275 % encoding.tolocal(ctx.branch()))
1276 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1276 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1277 edittext.extend([_("HG: added %s") % f for f in added])
1277 edittext.extend([_("HG: added %s") % f for f in added])
1278 edittext.extend([_("HG: changed %s") % f for f in modified])
1278 edittext.extend([_("HG: changed %s") % f for f in modified])
1279 edittext.extend([_("HG: removed %s") % f for f in removed])
1279 edittext.extend([_("HG: removed %s") % f for f in removed])
1280 if not added and not modified and not removed:
1280 if not added and not modified and not removed:
1281 edittext.append(_("HG: no files changed"))
1281 edittext.append(_("HG: no files changed"))
1282 edittext.append("")
1282 edittext.append("")
1283 # run editor in the repository root
1283 # run editor in the repository root
1284 olddir = os.getcwd()
1284 olddir = os.getcwd()
1285 os.chdir(repo.root)
1285 os.chdir(repo.root)
1286 text = repo.ui.edit("\n".join(edittext), ctx.user())
1286 text = repo.ui.edit("\n".join(edittext), ctx.user())
1287 text = re.sub("(?m)^HG:.*\n", "", text)
1287 text = re.sub("(?m)^HG:.*\n", "", text)
1288 os.chdir(olddir)
1288 os.chdir(olddir)
1289
1289
1290 if not text.strip():
1290 if not text.strip():
1291 raise util.Abort(_("empty commit message"))
1291 raise util.Abort(_("empty commit message"))
1292
1292
1293 return text
1293 return text
@@ -1,492 +1,495 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, urlparse, posixpath
8 import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath
9 from i18n import _
9 from i18n import _
10 import config, util, node, error, cmdutil
10 import config, util, node, error, cmdutil
11 hg = None
11 hg = None
12
12
13 nullstate = ('', '', 'empty')
13 nullstate = ('', '', 'empty')
14
14
15 def state(ctx, ui):
15 def state(ctx, ui):
16 """return a state dict, mapping subrepo paths configured in .hgsub
16 """return a state dict, mapping subrepo paths configured in .hgsub
17 to tuple: (source from .hgsub, revision from .hgsubstate, kind
17 to tuple: (source from .hgsub, revision from .hgsubstate, kind
18 (key in types dict))
18 (key in types dict))
19 """
19 """
20 p = config.config()
20 p = config.config()
21 def read(f, sections=None, remap=None):
21 def read(f, sections=None, remap=None):
22 if f in ctx:
22 if f in ctx:
23 p.parse(f, ctx[f].data(), sections, remap, read)
23 p.parse(f, ctx[f].data(), sections, remap, read)
24 else:
24 else:
25 raise util.Abort(_("subrepo spec file %s not found") % f)
25 raise util.Abort(_("subrepo spec file %s not found") % f)
26
26
27 if '.hgsub' in ctx:
27 if '.hgsub' in ctx:
28 read('.hgsub')
28 read('.hgsub')
29
29
30 for path, src in ui.configitems('subpaths'):
30 for path, src in ui.configitems('subpaths'):
31 p.set('subpaths', path, src, ui.configsource('subpaths', path))
31 p.set('subpaths', path, src, ui.configsource('subpaths', path))
32
32
33 rev = {}
33 rev = {}
34 if '.hgsubstate' in ctx:
34 if '.hgsubstate' in ctx:
35 try:
35 try:
36 for l in ctx['.hgsubstate'].data().splitlines():
36 for l in ctx['.hgsubstate'].data().splitlines():
37 revision, path = l.split(" ", 1)
37 revision, path = l.split(" ", 1)
38 rev[path] = revision
38 rev[path] = revision
39 except IOError, err:
39 except IOError, err:
40 if err.errno != errno.ENOENT:
40 if err.errno != errno.ENOENT:
41 raise
41 raise
42
42
43 state = {}
43 state = {}
44 for path, src in p[''].items():
44 for path, src in p[''].items():
45 kind = 'hg'
45 kind = 'hg'
46 if src.startswith('['):
46 if src.startswith('['):
47 if ']' not in src:
47 if ']' not in src:
48 raise util.Abort(_('missing ] in subrepo source'))
48 raise util.Abort(_('missing ] in subrepo source'))
49 kind, src = src.split(']', 1)
49 kind, src = src.split(']', 1)
50 kind = kind[1:]
50 kind = kind[1:]
51
51
52 for pattern, repl in p.items('subpaths'):
52 for pattern, repl in p.items('subpaths'):
53 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
53 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
54 # does a string decode.
54 # does a string decode.
55 repl = repl.encode('string-escape')
55 repl = repl.encode('string-escape')
56 # However, we still want to allow back references to go
56 # However, we still want to allow back references to go
57 # through unharmed, so we turn r'\\1' into r'\1'. Again,
57 # through unharmed, so we turn r'\\1' into r'\1'. Again,
58 # extra escapes are needed because re.sub string decodes.
58 # extra escapes are needed because re.sub string decodes.
59 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
59 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
60 try:
60 try:
61 src = re.sub(pattern, repl, src, 1)
61 src = re.sub(pattern, repl, src, 1)
62 except re.error, e:
62 except re.error, e:
63 raise util.Abort(_("bad subrepository pattern in %s: %s")
63 raise util.Abort(_("bad subrepository pattern in %s: %s")
64 % (p.source('subpaths', pattern), e))
64 % (p.source('subpaths', pattern), e))
65
65
66 state[path] = (src.strip(), rev.get(path, ''), kind)
66 state[path] = (src.strip(), rev.get(path, ''), kind)
67
67
68 return state
68 return state
69
69
70 def writestate(repo, state):
70 def writestate(repo, state):
71 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
71 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
72 repo.wwrite('.hgsubstate',
72 repo.wwrite('.hgsubstate',
73 ''.join(['%s %s\n' % (state[s][1], s)
73 ''.join(['%s %s\n' % (state[s][1], s)
74 for s in sorted(state)]), '')
74 for s in sorted(state)]), '')
75
75
76 def submerge(repo, wctx, mctx, actx):
76 def submerge(repo, wctx, mctx, actx):
77 """delegated from merge.applyupdates: merging of .hgsubstate file
77 """delegated from merge.applyupdates: merging of .hgsubstate file
78 in working context, merging context and ancestor context"""
78 in working context, merging context and ancestor context"""
79 if mctx == actx: # backwards?
79 if mctx == actx: # backwards?
80 actx = wctx.p1()
80 actx = wctx.p1()
81 s1 = wctx.substate
81 s1 = wctx.substate
82 s2 = mctx.substate
82 s2 = mctx.substate
83 sa = actx.substate
83 sa = actx.substate
84 sm = {}
84 sm = {}
85
85
86 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
86 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
87
87
88 def debug(s, msg, r=""):
88 def debug(s, msg, r=""):
89 if r:
89 if r:
90 r = "%s:%s:%s" % r
90 r = "%s:%s:%s" % r
91 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
91 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
92
92
93 for s, l in s1.items():
93 for s, l in s1.items():
94 a = sa.get(s, nullstate)
94 a = sa.get(s, nullstate)
95 ld = l # local state with possible dirty flag for compares
95 ld = l # local state with possible dirty flag for compares
96 if wctx.sub(s).dirty():
96 if wctx.sub(s).dirty():
97 ld = (l[0], l[1] + "+")
97 ld = (l[0], l[1] + "+")
98 if wctx == actx: # overwrite
98 if wctx == actx: # overwrite
99 a = ld
99 a = ld
100
100
101 if s in s2:
101 if s in s2:
102 r = s2[s]
102 r = s2[s]
103 if ld == r or r == a: # no change or local is newer
103 if ld == r or r == a: # no change or local is newer
104 sm[s] = l
104 sm[s] = l
105 continue
105 continue
106 elif ld == a: # other side changed
106 elif ld == a: # other side changed
107 debug(s, "other changed, get", r)
107 debug(s, "other changed, get", r)
108 wctx.sub(s).get(r)
108 wctx.sub(s).get(r)
109 sm[s] = r
109 sm[s] = r
110 elif ld[0] != r[0]: # sources differ
110 elif ld[0] != r[0]: # sources differ
111 if repo.ui.promptchoice(
111 if repo.ui.promptchoice(
112 _(' subrepository sources for %s differ\n'
112 _(' subrepository sources for %s differ\n'
113 'use (l)ocal source (%s) or (r)emote source (%s)?')
113 'use (l)ocal source (%s) or (r)emote source (%s)?')
114 % (s, l[0], r[0]),
114 % (s, l[0], r[0]),
115 (_('&Local'), _('&Remote')), 0):
115 (_('&Local'), _('&Remote')), 0):
116 debug(s, "prompt changed, get", r)
116 debug(s, "prompt changed, get", r)
117 wctx.sub(s).get(r)
117 wctx.sub(s).get(r)
118 sm[s] = r
118 sm[s] = r
119 elif ld[1] == a[1]: # local side is unchanged
119 elif ld[1] == a[1]: # local side is unchanged
120 debug(s, "other side changed, get", r)
120 debug(s, "other side changed, get", r)
121 wctx.sub(s).get(r)
121 wctx.sub(s).get(r)
122 sm[s] = r
122 sm[s] = r
123 else:
123 else:
124 debug(s, "both sides changed, merge with", r)
124 debug(s, "both sides changed, merge with", r)
125 wctx.sub(s).merge(r)
125 wctx.sub(s).merge(r)
126 sm[s] = l
126 sm[s] = l
127 elif ld == a: # remote removed, local unchanged
127 elif ld == a: # remote removed, local unchanged
128 debug(s, "remote removed, remove")
128 debug(s, "remote removed, remove")
129 wctx.sub(s).remove()
129 wctx.sub(s).remove()
130 else:
130 else:
131 if repo.ui.promptchoice(
131 if repo.ui.promptchoice(
132 _(' local changed subrepository %s which remote removed\n'
132 _(' local changed subrepository %s which remote removed\n'
133 'use (c)hanged version or (d)elete?') % s,
133 'use (c)hanged version or (d)elete?') % s,
134 (_('&Changed'), _('&Delete')), 0):
134 (_('&Changed'), _('&Delete')), 0):
135 debug(s, "prompt remove")
135 debug(s, "prompt remove")
136 wctx.sub(s).remove()
136 wctx.sub(s).remove()
137
137
138 for s, r in s2.items():
138 for s, r in s2.items():
139 if s in s1:
139 if s in s1:
140 continue
140 continue
141 elif s not in sa:
141 elif s not in sa:
142 debug(s, "remote added, get", r)
142 debug(s, "remote added, get", r)
143 mctx.sub(s).get(r)
143 mctx.sub(s).get(r)
144 sm[s] = r
144 sm[s] = r
145 elif r != sa[s]:
145 elif r != sa[s]:
146 if repo.ui.promptchoice(
146 if repo.ui.promptchoice(
147 _(' remote changed subrepository %s which local removed\n'
147 _(' remote changed subrepository %s which local removed\n'
148 'use (c)hanged version or (d)elete?') % s,
148 'use (c)hanged version or (d)elete?') % s,
149 (_('&Changed'), _('&Delete')), 0) == 0:
149 (_('&Changed'), _('&Delete')), 0) == 0:
150 debug(s, "prompt recreate", r)
150 debug(s, "prompt recreate", r)
151 wctx.sub(s).get(r)
151 wctx.sub(s).get(r)
152 sm[s] = r
152 sm[s] = r
153
153
154 # record merged .hgsubstate
154 # record merged .hgsubstate
155 writestate(repo, sm)
155 writestate(repo, sm)
156
156
157 def relpath(sub):
157 def relpath(sub):
158 """return path to this subrepo as seen from outermost repo"""
158 """return path to this subrepo as seen from outermost repo"""
159 if not hasattr(sub, '_repo'):
159 if not hasattr(sub, '_repo'):
160 return sub._path
160 return sub._path
161 parent = sub._repo
161 parent = sub._repo
162 while hasattr(parent, '_subparent'):
162 while hasattr(parent, '_subparent'):
163 parent = parent._subparent
163 parent = parent._subparent
164 return sub._repo.root[len(parent.root)+1:]
164 return sub._repo.root[len(parent.root)+1:]
165
165
166 def _abssource(repo, push=False):
166 def _abssource(repo, push=False):
167 """return pull/push path of repo - either based on parent repo
167 """return pull/push path of repo - either based on parent repo
168 .hgsub info or on the subrepos own config"""
168 .hgsub info or on the subrepos own config"""
169 if hasattr(repo, '_subparent'):
169 if hasattr(repo, '_subparent'):
170 source = repo._subsource
170 source = repo._subsource
171 if source.startswith('/') or '://' in source:
171 if source.startswith('/') or '://' in source:
172 return source
172 return source
173 parent = _abssource(repo._subparent, push)
173 parent = _abssource(repo._subparent, push)
174 if '://' in parent:
174 if '://' in parent:
175 if parent[-1] == '/':
175 if parent[-1] == '/':
176 parent = parent[:-1]
176 parent = parent[:-1]
177 r = urlparse.urlparse(parent + '/' + source)
177 r = urlparse.urlparse(parent + '/' + source)
178 r = urlparse.urlunparse((r[0], r[1],
178 r = urlparse.urlunparse((r[0], r[1],
179 posixpath.normpath(r[2]),
179 posixpath.normpath(r[2]),
180 r[3], r[4], r[5]))
180 r[3], r[4], r[5]))
181 return r
181 return r
182 return posixpath.normpath(os.path.join(parent, repo._subsource))
182 return posixpath.normpath(os.path.join(parent, repo._subsource))
183 if push and repo.ui.config('paths', 'default-push'):
183 if push and repo.ui.config('paths', 'default-push'):
184 return repo.ui.config('paths', 'default-push', repo.root)
184 return repo.ui.config('paths', 'default-push', repo.root)
185 return repo.ui.config('paths', 'default', repo.root)
185 return repo.ui.config('paths', 'default', repo.root)
186
186
187 def itersubrepos(ctx1, ctx2):
187 def itersubrepos(ctx1, ctx2):
188 """find subrepos in ctx1 or ctx2"""
188 """find subrepos in ctx1 or ctx2"""
189 # Create a (subpath, ctx) mapping where we prefer subpaths from
189 # Create a (subpath, ctx) mapping where we prefer subpaths from
190 # ctx1. The subpaths from ctx2 are important when the .hgsub file
190 # ctx1. The subpaths from ctx2 are important when the .hgsub file
191 # has been modified (in ctx2) but not yet committed (in ctx1).
191 # has been modified (in ctx2) but not yet committed (in ctx1).
192 subpaths = dict.fromkeys(ctx2.substate, ctx2)
192 subpaths = dict.fromkeys(ctx2.substate, ctx2)
193 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
193 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
194 for subpath, ctx in sorted(subpaths.iteritems()):
194 for subpath, ctx in sorted(subpaths.iteritems()):
195 yield subpath, ctx.sub(subpath)
195 yield subpath, ctx.sub(subpath)
196
196
197 def subrepo(ctx, path):
197 def subrepo(ctx, path):
198 """return instance of the right subrepo class for subrepo in path"""
198 """return instance of the right subrepo class for subrepo in path"""
199 # subrepo inherently violates our import layering rules
199 # subrepo inherently violates our import layering rules
200 # because it wants to make repo objects from deep inside the stack
200 # because it wants to make repo objects from deep inside the stack
201 # so we manually delay the circular imports to not break
201 # so we manually delay the circular imports to not break
202 # scripts that don't use our demand-loading
202 # scripts that don't use our demand-loading
203 global hg
203 global hg
204 import hg as h
204 import hg as h
205 hg = h
205 hg = h
206
206
207 util.path_auditor(ctx._repo.root)(path)
207 util.path_auditor(ctx._repo.root)(path)
208 state = ctx.substate.get(path, nullstate)
208 state = ctx.substate.get(path, nullstate)
209 if state[2] not in types:
209 if state[2] not in types:
210 raise util.Abort(_('unknown subrepo type %s') % state[2])
210 raise util.Abort(_('unknown subrepo type %s') % state[2])
211 return types[state[2]](ctx, path, state[:2])
211 return types[state[2]](ctx, path, state[:2])
212
212
213 # subrepo classes need to implement the following abstract class:
213 # subrepo classes need to implement the following abstract class:
214
214
215 class abstractsubrepo(object):
215 class abstractsubrepo(object):
216
216
217 def dirty(self):
217 def dirty(self):
218 """returns true if the dirstate of the subrepo does not match
218 """returns true if the dirstate of the subrepo does not match
219 current stored state
219 current stored state
220 """
220 """
221 raise NotImplementedError
221 raise NotImplementedError
222
222
223 def checknested(path):
223 def checknested(path):
224 """check if path is a subrepository within this repository"""
224 """check if path is a subrepository within this repository"""
225 return False
225 return False
226
226
227 def commit(self, text, user, date):
227 def commit(self, text, user, date):
228 """commit the current changes to the subrepo with the given
228 """commit the current changes to the subrepo with the given
229 log message. Use given user and date if possible. Return the
229 log message. Use given user and date if possible. Return the
230 new state of the subrepo.
230 new state of the subrepo.
231 """
231 """
232 raise NotImplementedError
232 raise NotImplementedError
233
233
234 def remove(self):
234 def remove(self):
235 """remove the subrepo
235 """remove the subrepo
236
236
237 (should verify the dirstate is not dirty first)
237 (should verify the dirstate is not dirty first)
238 """
238 """
239 raise NotImplementedError
239 raise NotImplementedError
240
240
241 def get(self, state):
241 def get(self, state):
242 """run whatever commands are needed to put the subrepo into
242 """run whatever commands are needed to put the subrepo into
243 this state
243 this state
244 """
244 """
245 raise NotImplementedError
245 raise NotImplementedError
246
246
247 def merge(self, state):
247 def merge(self, state):
248 """merge currently-saved state with the new state."""
248 """merge currently-saved state with the new state."""
249 raise NotImplementedError
249 raise NotImplementedError
250
250
251 def push(self, force):
251 def push(self, force):
252 """perform whatever action is analogous to 'hg push'
252 """perform whatever action is analogous to 'hg push'
253
253
254 This may be a no-op on some systems.
254 This may be a no-op on some systems.
255 """
255 """
256 raise NotImplementedError
256 raise NotImplementedError
257
257
258
258
259 def status(self, rev2, **opts):
259 def status(self, rev2, **opts):
260 return [], [], [], [], [], [], []
260 return [], [], [], [], [], [], []
261
261
262 def diff(self, diffopts, node2, match, prefix, **opts):
262 def diff(self, diffopts, node2, match, prefix, **opts):
263 pass
263 pass
264
264
265 class hgsubrepo(abstractsubrepo):
265 class hgsubrepo(abstractsubrepo):
266 def __init__(self, ctx, path, state):
266 def __init__(self, ctx, path, state):
267 self._path = path
267 self._path = path
268 self._state = state
268 self._state = state
269 r = ctx._repo
269 r = ctx._repo
270 root = r.wjoin(path)
270 root = r.wjoin(path)
271 create = False
271 create = False
272 if not os.path.exists(os.path.join(root, '.hg')):
272 if not os.path.exists(os.path.join(root, '.hg')):
273 create = True
273 create = True
274 util.makedirs(root)
274 util.makedirs(root)
275 self._repo = hg.repository(r.ui, root, create=create)
275 self._repo = hg.repository(r.ui, root, create=create)
276 self._repo._subparent = r
276 self._repo._subparent = r
277 self._repo._subsource = state[0]
277 self._repo._subsource = state[0]
278
278
279 if create:
279 if create:
280 fp = self._repo.opener("hgrc", "w", text=True)
280 fp = self._repo.opener("hgrc", "w", text=True)
281 fp.write('[paths]\n')
281 fp.write('[paths]\n')
282
282
283 def addpathconfig(key, value):
283 def addpathconfig(key, value):
284 fp.write('%s = %s\n' % (key, value))
284 fp.write('%s = %s\n' % (key, value))
285 self._repo.ui.setconfig('paths', key, value)
285 self._repo.ui.setconfig('paths', key, value)
286
286
287 defpath = _abssource(self._repo)
287 defpath = _abssource(self._repo)
288 defpushpath = _abssource(self._repo, True)
288 defpushpath = _abssource(self._repo, True)
289 addpathconfig('default', defpath)
289 addpathconfig('default', defpath)
290 if defpath != defpushpath:
290 if defpath != defpushpath:
291 addpathconfig('default-push', defpushpath)
291 addpathconfig('default-push', defpushpath)
292 fp.close()
292 fp.close()
293
293
294 def status(self, rev2, **opts):
294 def status(self, rev2, **opts):
295 try:
295 try:
296 rev1 = self._state[1]
296 rev1 = self._state[1]
297 ctx1 = self._repo[rev1]
297 ctx1 = self._repo[rev1]
298 ctx2 = self._repo[rev2]
298 ctx2 = self._repo[rev2]
299 return self._repo.status(ctx1, ctx2, **opts)
299 return self._repo.status(ctx1, ctx2, **opts)
300 except error.RepoLookupError, inst:
300 except error.RepoLookupError, inst:
301 self._repo.ui.warn(_("warning: %s in %s\n")
301 self._repo.ui.warn(_("warning: %s in %s\n")
302 % (inst, relpath(self)))
302 % (inst, relpath(self)))
303 return [], [], [], [], [], [], []
303 return [], [], [], [], [], [], []
304
304
305 def diff(self, diffopts, node2, match, prefix, **opts):
305 def diff(self, diffopts, node2, match, prefix, **opts):
306 try:
306 try:
307 node1 = node.bin(self._state[1])
307 node1 = node.bin(self._state[1])
308 # We currently expect node2 to come from substate and be
309 # in hex format
310 node2 = node.bin(node2)
308 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
311 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
309 node1, node2, match,
312 node1, node2, match,
310 prefix=os.path.join(prefix, self._path),
313 prefix=os.path.join(prefix, self._path),
311 listsubrepos=True, **opts)
314 listsubrepos=True, **opts)
312 except error.RepoLookupError, inst:
315 except error.RepoLookupError, inst:
313 self._repo.ui.warn(_("warning: %s in %s\n")
316 self._repo.ui.warn(_("warning: %s in %s\n")
314 % (inst, relpath(self)))
317 % (inst, relpath(self)))
315
318
316 def dirty(self):
319 def dirty(self):
317 r = self._state[1]
320 r = self._state[1]
318 if r == '':
321 if r == '':
319 return True
322 return True
320 w = self._repo[None]
323 w = self._repo[None]
321 if w.p1() != self._repo[r]: # version checked out change
324 if w.p1() != self._repo[r]: # version checked out change
322 return True
325 return True
323 return w.dirty() # working directory changed
326 return w.dirty() # working directory changed
324
327
325 def checknested(self, path):
328 def checknested(self, path):
326 return self._repo._checknested(self._repo.wjoin(path))
329 return self._repo._checknested(self._repo.wjoin(path))
327
330
328 def commit(self, text, user, date):
331 def commit(self, text, user, date):
329 self._repo.ui.debug("committing subrepo %s\n" % relpath(self))
332 self._repo.ui.debug("committing subrepo %s\n" % relpath(self))
330 n = self._repo.commit(text, user, date)
333 n = self._repo.commit(text, user, date)
331 if not n:
334 if not n:
332 return self._repo['.'].hex() # different version checked out
335 return self._repo['.'].hex() # different version checked out
333 return node.hex(n)
336 return node.hex(n)
334
337
335 def remove(self):
338 def remove(self):
336 # we can't fully delete the repository as it may contain
339 # we can't fully delete the repository as it may contain
337 # local-only history
340 # local-only history
338 self._repo.ui.note(_('removing subrepo %s\n') % relpath(self))
341 self._repo.ui.note(_('removing subrepo %s\n') % relpath(self))
339 hg.clean(self._repo, node.nullid, False)
342 hg.clean(self._repo, node.nullid, False)
340
343
341 def _get(self, state):
344 def _get(self, state):
342 source, revision, kind = state
345 source, revision, kind = state
343 try:
346 try:
344 self._repo.lookup(revision)
347 self._repo.lookup(revision)
345 except error.RepoError:
348 except error.RepoError:
346 self._repo._subsource = source
349 self._repo._subsource = source
347 srcurl = _abssource(self._repo)
350 srcurl = _abssource(self._repo)
348 self._repo.ui.status(_('pulling subrepo %s from %s\n')
351 self._repo.ui.status(_('pulling subrepo %s from %s\n')
349 % (relpath(self), srcurl))
352 % (relpath(self), srcurl))
350 other = hg.repository(self._repo.ui, srcurl)
353 other = hg.repository(self._repo.ui, srcurl)
351 self._repo.pull(other)
354 self._repo.pull(other)
352
355
353 def get(self, state):
356 def get(self, state):
354 self._get(state)
357 self._get(state)
355 source, revision, kind = state
358 source, revision, kind = state
356 self._repo.ui.debug("getting subrepo %s\n" % self._path)
359 self._repo.ui.debug("getting subrepo %s\n" % self._path)
357 hg.clean(self._repo, revision, False)
360 hg.clean(self._repo, revision, False)
358
361
359 def merge(self, state):
362 def merge(self, state):
360 self._get(state)
363 self._get(state)
361 cur = self._repo['.']
364 cur = self._repo['.']
362 dst = self._repo[state[1]]
365 dst = self._repo[state[1]]
363 anc = dst.ancestor(cur)
366 anc = dst.ancestor(cur)
364 if anc == cur:
367 if anc == cur:
365 self._repo.ui.debug("updating subrepo %s\n" % relpath(self))
368 self._repo.ui.debug("updating subrepo %s\n" % relpath(self))
366 hg.update(self._repo, state[1])
369 hg.update(self._repo, state[1])
367 elif anc == dst:
370 elif anc == dst:
368 self._repo.ui.debug("skipping subrepo %s\n" % relpath(self))
371 self._repo.ui.debug("skipping subrepo %s\n" % relpath(self))
369 else:
372 else:
370 self._repo.ui.debug("merging subrepo %s\n" % relpath(self))
373 self._repo.ui.debug("merging subrepo %s\n" % relpath(self))
371 hg.merge(self._repo, state[1], remind=False)
374 hg.merge(self._repo, state[1], remind=False)
372
375
373 def push(self, force):
376 def push(self, force):
374 # push subrepos depth-first for coherent ordering
377 # push subrepos depth-first for coherent ordering
375 c = self._repo['']
378 c = self._repo['']
376 subs = c.substate # only repos that are committed
379 subs = c.substate # only repos that are committed
377 for s in sorted(subs):
380 for s in sorted(subs):
378 if not c.sub(s).push(force):
381 if not c.sub(s).push(force):
379 return False
382 return False
380
383
381 dsturl = _abssource(self._repo, True)
384 dsturl = _abssource(self._repo, True)
382 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
385 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
383 (relpath(self), dsturl))
386 (relpath(self), dsturl))
384 other = hg.repository(self._repo.ui, dsturl)
387 other = hg.repository(self._repo.ui, dsturl)
385 return self._repo.push(other, force)
388 return self._repo.push(other, force)
386
389
387 class svnsubrepo(abstractsubrepo):
390 class svnsubrepo(abstractsubrepo):
388 def __init__(self, ctx, path, state):
391 def __init__(self, ctx, path, state):
389 self._path = path
392 self._path = path
390 self._state = state
393 self._state = state
391 self._ctx = ctx
394 self._ctx = ctx
392 self._ui = ctx._repo.ui
395 self._ui = ctx._repo.ui
393
396
394 def _svncommand(self, commands, filename=''):
397 def _svncommand(self, commands, filename=''):
395 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
398 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
396 cmd = ['svn'] + commands + [path]
399 cmd = ['svn'] + commands + [path]
397 cmd = [util.shellquote(arg) for arg in cmd]
400 cmd = [util.shellquote(arg) for arg in cmd]
398 cmd = util.quotecommand(' '.join(cmd))
401 cmd = util.quotecommand(' '.join(cmd))
399 env = dict(os.environ)
402 env = dict(os.environ)
400 # Avoid localized output, preserve current locale for everything else.
403 # Avoid localized output, preserve current locale for everything else.
401 env['LC_MESSAGES'] = 'C'
404 env['LC_MESSAGES'] = 'C'
402 write, read, err = util.popen3(cmd, env=env, newlines=True)
405 write, read, err = util.popen3(cmd, env=env, newlines=True)
403 retdata = read.read()
406 retdata = read.read()
404 err = err.read().strip()
407 err = err.read().strip()
405 if err:
408 if err:
406 raise util.Abort(err)
409 raise util.Abort(err)
407 return retdata
410 return retdata
408
411
409 def _wcrev(self):
412 def _wcrev(self):
410 output = self._svncommand(['info', '--xml'])
413 output = self._svncommand(['info', '--xml'])
411 doc = xml.dom.minidom.parseString(output)
414 doc = xml.dom.minidom.parseString(output)
412 entries = doc.getElementsByTagName('entry')
415 entries = doc.getElementsByTagName('entry')
413 if not entries:
416 if not entries:
414 return 0
417 return 0
415 return int(entries[0].getAttribute('revision') or 0)
418 return int(entries[0].getAttribute('revision') or 0)
416
419
417 def _wcchanged(self):
420 def _wcchanged(self):
418 """Return (changes, extchanges) where changes is True
421 """Return (changes, extchanges) where changes is True
419 if the working directory was changed, and extchanges is
422 if the working directory was changed, and extchanges is
420 True if any of these changes concern an external entry.
423 True if any of these changes concern an external entry.
421 """
424 """
422 output = self._svncommand(['status', '--xml'])
425 output = self._svncommand(['status', '--xml'])
423 externals, changes = [], []
426 externals, changes = [], []
424 doc = xml.dom.minidom.parseString(output)
427 doc = xml.dom.minidom.parseString(output)
425 for e in doc.getElementsByTagName('entry'):
428 for e in doc.getElementsByTagName('entry'):
426 s = e.getElementsByTagName('wc-status')
429 s = e.getElementsByTagName('wc-status')
427 if not s:
430 if not s:
428 continue
431 continue
429 item = s[0].getAttribute('item')
432 item = s[0].getAttribute('item')
430 props = s[0].getAttribute('props')
433 props = s[0].getAttribute('props')
431 path = e.getAttribute('path')
434 path = e.getAttribute('path')
432 if item == 'external':
435 if item == 'external':
433 externals.append(path)
436 externals.append(path)
434 if (item not in ('', 'normal', 'unversioned', 'external')
437 if (item not in ('', 'normal', 'unversioned', 'external')
435 or props not in ('', 'none')):
438 or props not in ('', 'none')):
436 changes.append(path)
439 changes.append(path)
437 for path in changes:
440 for path in changes:
438 for ext in externals:
441 for ext in externals:
439 if path == ext or path.startswith(ext + os.sep):
442 if path == ext or path.startswith(ext + os.sep):
440 return True, True
443 return True, True
441 return bool(changes), False
444 return bool(changes), False
442
445
443 def dirty(self):
446 def dirty(self):
444 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
447 if self._wcrev() == self._state[1] and not self._wcchanged()[0]:
445 return False
448 return False
446 return True
449 return True
447
450
448 def commit(self, text, user, date):
451 def commit(self, text, user, date):
449 # user and date are out of our hands since svn is centralized
452 # user and date are out of our hands since svn is centralized
450 changed, extchanged = self._wcchanged()
453 changed, extchanged = self._wcchanged()
451 if not changed:
454 if not changed:
452 return self._wcrev()
455 return self._wcrev()
453 if extchanged:
456 if extchanged:
454 # Do not try to commit externals
457 # Do not try to commit externals
455 raise util.Abort(_('cannot commit svn externals'))
458 raise util.Abort(_('cannot commit svn externals'))
456 commitinfo = self._svncommand(['commit', '-m', text])
459 commitinfo = self._svncommand(['commit', '-m', text])
457 self._ui.status(commitinfo)
460 self._ui.status(commitinfo)
458 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
461 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
459 if not newrev:
462 if not newrev:
460 raise util.Abort(commitinfo.splitlines()[-1])
463 raise util.Abort(commitinfo.splitlines()[-1])
461 newrev = newrev.groups()[0]
464 newrev = newrev.groups()[0]
462 self._ui.status(self._svncommand(['update', '-r', newrev]))
465 self._ui.status(self._svncommand(['update', '-r', newrev]))
463 return newrev
466 return newrev
464
467
465 def remove(self):
468 def remove(self):
466 if self.dirty():
469 if self.dirty():
467 self._ui.warn(_('not removing repo %s because '
470 self._ui.warn(_('not removing repo %s because '
468 'it has changes.\n' % self._path))
471 'it has changes.\n' % self._path))
469 return
472 return
470 self._ui.note(_('removing subrepo %s\n') % self._path)
473 self._ui.note(_('removing subrepo %s\n') % self._path)
471 shutil.rmtree(self._ctx.repo.join(self._path))
474 shutil.rmtree(self._ctx.repo.join(self._path))
472
475
473 def get(self, state):
476 def get(self, state):
474 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
477 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
475 if not re.search('Checked out revision [0-9]+.', status):
478 if not re.search('Checked out revision [0-9]+.', status):
476 raise util.Abort(status.splitlines()[-1])
479 raise util.Abort(status.splitlines()[-1])
477 self._ui.status(status)
480 self._ui.status(status)
478
481
479 def merge(self, state):
482 def merge(self, state):
480 old = int(self._state[1])
483 old = int(self._state[1])
481 new = int(state[1])
484 new = int(state[1])
482 if new > old:
485 if new > old:
483 self.get(state)
486 self.get(state)
484
487
485 def push(self, force):
488 def push(self, force):
486 # push is a no-op for SVN
489 # push is a no-op for SVN
487 return True
490 return True
488
491
489 types = {
492 types = {
490 'hg': hgsubrepo,
493 'hg': hgsubrepo,
491 'svn': svnsubrepo,
494 'svn': svnsubrepo,
492 }
495 }
@@ -1,193 +1,204 b''
1 $ "$TESTDIR/hghave" svn || exit 80
1 $ "$TESTDIR/hghave" svn || exit 80
2
2
3 $ fix_path()
3 $ fix_path()
4 > {
4 > {
5 > tr '\\' /
5 > tr '\\' /
6 > }
6 > }
7
7
8 $ escapedwd=`pwd | fix_path`
8 $ escapedwd=`pwd | fix_path`
9
9
10 SVN wants all paths to start with a slash. Unfortunately, Windows ones
10 SVN wants all paths to start with a slash. Unfortunately, Windows ones
11 don't. Handle that.
11 don't. Handle that.
12
12
13 $ expr "$escapedwd" : "\/" > /dev/null
13 $ expr "$escapedwd" : "\/" > /dev/null
14 $ if [ $? -ne 0 ]; then
14 $ if [ $? -ne 0 ]; then
15 > escapedwd="/$escapedwd"
15 > escapedwd="/$escapedwd"
16 > fi
16 > fi
17 $ escapedwd=`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$escapedwd"`
17 $ escapedwd=`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$escapedwd"`
18 $ filterpath="s|$escapedwd|/root|"
18 $ filterpath="s|$escapedwd|/root|"
19 $ filteroutofdate='s/ in transaction.*/ is out of date/;s/Out of date: /File /'
19 $ filteroutofdate='s/ in transaction.*/ is out of date/;s/Out of date: /File /'
20
20
21 create subversion repo
21 create subversion repo
22
22
23 $ SVNREPO="file://$escapedwd/svn-repo"
23 $ SVNREPO="file://$escapedwd/svn-repo"
24 $ WCROOT="`pwd`/svn-wc"
24 $ WCROOT="`pwd`/svn-wc"
25 $ svnadmin create svn-repo
25 $ svnadmin create svn-repo
26 $ svn co "$SVNREPO" svn-wc
26 $ svn co "$SVNREPO" svn-wc
27 Checked out revision 0.
27 Checked out revision 0.
28 $ cd svn-wc
28 $ cd svn-wc
29 $ mkdir src
29 $ mkdir src
30 $ echo alpha > src/alpha
30 $ echo alpha > src/alpha
31 $ svn add src
31 $ svn add src
32 A src
32 A src
33 A src/alpha
33 A src/alpha
34 $ mkdir externals
34 $ mkdir externals
35 $ echo other > externals/other
35 $ echo other > externals/other
36 $ svn add externals
36 $ svn add externals
37 A externals
37 A externals
38 A externals/other
38 A externals/other
39 $ svn ci -m 'Add alpha'
39 $ svn ci -m 'Add alpha'
40 Adding externals
40 Adding externals
41 Adding externals/other
41 Adding externals/other
42 Adding src
42 Adding src
43 Adding src/alpha
43 Adding src/alpha
44 Transmitting file data ..
44 Transmitting file data ..
45 Committed revision 1.
45 Committed revision 1.
46 $ svn up
46 $ svn up
47 At revision 1.
47 At revision 1.
48 $ echo "externals -r1 $SVNREPO/externals" > extdef
48 $ echo "externals -r1 $SVNREPO/externals" > extdef
49 $ svn propset -F extdef svn:externals src
49 $ svn propset -F extdef svn:externals src
50 property 'svn:externals' set on 'src'
50 property 'svn:externals' set on 'src'
51 $ svn ci -m 'Setting externals'
51 $ svn ci -m 'Setting externals'
52 Sending src
52 Sending src
53
53
54 Committed revision 2.
54 Committed revision 2.
55 $ cd ..
55 $ cd ..
56
56
57 create hg repo
57 create hg repo
58
58
59 $ mkdir sub
59 $ mkdir sub
60 $ cd sub
60 $ cd sub
61 $ hg init t
61 $ hg init t
62 $ cd t
62 $ cd t
63
63
64 first revision, no sub
64 first revision, no sub
65
65
66 $ echo a > a
66 $ echo a > a
67 $ hg ci -Am0
67 $ hg ci -Am0
68 adding a
68 adding a
69
69
70 add first svn sub with leading whitespaces
70 add first svn sub with leading whitespaces
71
71
72 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
72 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
73 $ svn co --quiet "$SVNREPO"/src s
73 $ svn co --quiet "$SVNREPO"/src s
74 $ hg add .hgsub
74 $ hg add .hgsub
75 $ hg ci -m1
75 $ hg ci -m1
76 committing subrepository s
76 committing subrepository s
77
77
78 debugsub
78 debugsub
79
79
80 $ hg debugsub | sed "$filterpath"
80 $ hg debugsub | sed "$filterpath"
81 path s
81 path s
82 source file:///root/svn-repo/src
82 source file:///root/svn-repo/src
83 revision 2
83 revision 2
84
84
85 change file in svn and hg, commit
85 change file in svn and hg, commit
86
86
87 $ echo a >> a
87 $ echo a >> a
88 $ echo alpha >> s/alpha
88 $ echo alpha >> s/alpha
89 $ hg commit -m 'Message!' \
89 $ hg commit -m 'Message!' \
90 > | sed 's:Sending.*s/alpha:Sending s/alpha:g'
90 > | sed 's:Sending.*s/alpha:Sending s/alpha:g'
91 committing subrepository s
91 committing subrepository s
92 Sending s/alpha
92 Sending s/alpha
93 Transmitting file data .
93 Transmitting file data .
94 Committed revision 3.
94 Committed revision 3.
95
95
96 Fetching external item into '.*/s/externals'
96 Fetching external item into '.*/s/externals'
97 External at revision 1.
97 External at revision 1.
98
98
99 At revision 3.
99 At revision 3.
100 $ hg debugsub | sed "$filterpath"
100 $ hg debugsub | sed "$filterpath"
101 path s
101 path s
102 source file:///root/svn-repo/src
102 source file:///root/svn-repo/src
103 revision 3
103 revision 3
104
104
105 $ echo a > s/a
105 $ echo a > s/a
106
106
107 should be empty despite change to s/a
107 should be empty despite change to s/a
108
108
109 $ hg st
109 $ hg st
110
110
111 add a commit from svn
111 add a commit from svn
112
112
113 $ cd "$WCROOT"/src
113 $ cd "$WCROOT"/src
114 $ svn up
114 $ svn up
115 U alpha
115 U alpha
116
116
117 Fetching external item into 'externals'
117 Fetching external item into 'externals'
118 A externals/other
118 A externals/other
119 Updated external to revision 1.
119 Updated external to revision 1.
120
120
121 Updated to revision 3.
121 Updated to revision 3.
122 $ echo xyz >> alpha
122 $ echo xyz >> alpha
123 $ svn propset svn:mime-type 'text/xml' alpha
123 $ svn propset svn:mime-type 'text/xml' alpha
124 property 'svn:mime-type' set on 'alpha'
124 property 'svn:mime-type' set on 'alpha'
125 $ svn ci -m 'amend a from svn'
125 $ svn ci -m 'amend a from svn'
126 Sending src/alpha
126 Sending src/alpha
127 Transmitting file data .
127 Transmitting file data .
128 Committed revision 4.
128 Committed revision 4.
129 $ cd ../../sub/t
129 $ cd ../../sub/t
130
130
131 this commit from hg will fail
131 this commit from hg will fail
132
132
133 $ echo zzz >> s/alpha
133 $ echo zzz >> s/alpha
134 $ hg ci -m 'amend alpha from hg' 2>&1 | sed "$filteroutofdate"
134 $ hg ci -m 'amend alpha from hg' 2>&1 | sed "$filteroutofdate"
135 committing subrepository s
135 committing subrepository s
136 abort: svn: Commit failed (details follow):
136 abort: svn: Commit failed (details follow):
137 svn: File '/src/alpha' is out of date
137 svn: File '/src/alpha' is out of date
138 $ svn revert -q s/alpha
138 $ svn revert -q s/alpha
139
139
140 this commit fails because of meta changes
140 this commit fails because of meta changes
141
141
142 $ svn propset svn:mime-type 'text/html' s/alpha
142 $ svn propset svn:mime-type 'text/html' s/alpha
143 property 'svn:mime-type' set on 's/alpha'
143 property 'svn:mime-type' set on 's/alpha'
144 $ hg ci -m 'amend alpha from hg' 2>&1 | sed "$filteroutofdate"
144 $ hg ci -m 'amend alpha from hg' 2>&1 | sed "$filteroutofdate"
145 committing subrepository s
145 committing subrepository s
146 abort: svn: Commit failed (details follow):
146 abort: svn: Commit failed (details follow):
147 svn: File '/src/alpha' is out of date
147 svn: File '/src/alpha' is out of date
148 $ svn revert -q s/alpha
148 $ svn revert -q s/alpha
149
149
150 this commit fails because of externals changes
150 this commit fails because of externals changes
151
151
152 $ echo zzz > s/externals/other
152 $ echo zzz > s/externals/other
153 $ hg ci -m 'amend externals from hg'
153 $ hg ci -m 'amend externals from hg'
154 committing subrepository s
154 committing subrepository s
155 abort: cannot commit svn externals
155 abort: cannot commit svn externals
156 $ hg diff --subrepos -r 1:2 | grep -v diff
157 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
158 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
159 @@ -1,1 +1,1 @@
160 -2 s
161 +3 s
162 --- a/a Thu Jan 01 00:00:00 1970 +0000
163 +++ b/a Thu Jan 01 00:00:00 1970 +0000
164 @@ -1,1 +1,2 @@
165 a
166 +a
156 $ svn revert -q s/externals/other
167 $ svn revert -q s/externals/other
157
168
158 this commit fails because of externals meta changes
169 this commit fails because of externals meta changes
159
170
160 $ svn propset svn:mime-type 'text/html' s/externals/other
171 $ svn propset svn:mime-type 'text/html' s/externals/other
161 property 'svn:mime-type' set on 's/externals/other'
172 property 'svn:mime-type' set on 's/externals/other'
162 $ hg ci -m 'amend externals from hg'
173 $ hg ci -m 'amend externals from hg'
163 committing subrepository s
174 committing subrepository s
164 abort: cannot commit svn externals
175 abort: cannot commit svn externals
165 $ svn revert -q s/externals/other
176 $ svn revert -q s/externals/other
166
177
167 clone
178 clone
168
179
169 $ cd ..
180 $ cd ..
170 $ hg clone t tc | fix_path
181 $ hg clone t tc | fix_path
171 updating to branch default
182 updating to branch default
172 A tc/s/alpha
183 A tc/s/alpha
173 U tc/s
184 U tc/s
174
185
175 Fetching external item into 'tc/s/externals'
186 Fetching external item into 'tc/s/externals'
176 A tc/s/externals/other
187 A tc/s/externals/other
177 Checked out external at revision 1.
188 Checked out external at revision 1.
178
189
179 Checked out revision 3.
190 Checked out revision 3.
180 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
181 $ cd tc
192 $ cd tc
182
193
183 debugsub in clone
194 debugsub in clone
184
195
185 $ hg debugsub | sed "$filterpath"
196 $ hg debugsub | sed "$filterpath"
186 path s
197 path s
187 source file:///root/svn-repo/src
198 source file:///root/svn-repo/src
188 revision 3
199 revision 3
189
200
190 verify subrepo is contained within the repo directory
201 verify subrepo is contained within the repo directory
191
202
192 $ python -c "import os.path; print os.path.exists('s')"
203 $ python -c "import os.path; print os.path.exists('s')"
193 True
204 True
General Comments 0
You need to be logged in to leave comments. Login now