##// END OF EJS Templates
subrepo: fix svnsubrepo.dirty() checking of ignoreupdate (issue2499)...
Kevin Bullock -
r13242:a8cef95c default
parent child Browse files
Show More
@@ -1,933 +1,933 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 import stat, subprocess, tarfile
9 import stat, subprocess, tarfile
10 from i18n import _
10 from i18n import _
11 import config, util, node, error, cmdutil
11 import config, util, node, error, cmdutil
12 hg = None
12 hg = None
13
13
14 nullstate = ('', '', 'empty')
14 nullstate = ('', '', 'empty')
15
15
16 def state(ctx, ui):
16 def state(ctx, ui):
17 """return a state dict, mapping subrepo paths configured in .hgsub
17 """return a state dict, mapping subrepo paths configured in .hgsub
18 to tuple: (source from .hgsub, revision from .hgsubstate, kind
18 to tuple: (source from .hgsub, revision from .hgsubstate, kind
19 (key in types dict))
19 (key in types dict))
20 """
20 """
21 p = config.config()
21 p = config.config()
22 def read(f, sections=None, remap=None):
22 def read(f, sections=None, remap=None):
23 if f in ctx:
23 if f in ctx:
24 try:
24 try:
25 data = ctx[f].data()
25 data = ctx[f].data()
26 except IOError, err:
26 except IOError, err:
27 if err.errno != errno.ENOENT:
27 if err.errno != errno.ENOENT:
28 raise
28 raise
29 # handle missing subrepo spec files as removed
29 # handle missing subrepo spec files as removed
30 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
30 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
31 return
31 return
32 p.parse(f, data, sections, remap, read)
32 p.parse(f, data, sections, remap, read)
33 else:
33 else:
34 raise util.Abort(_("subrepo spec file %s not found") % f)
34 raise util.Abort(_("subrepo spec file %s not found") % f)
35
35
36 if '.hgsub' in ctx:
36 if '.hgsub' in ctx:
37 read('.hgsub')
37 read('.hgsub')
38
38
39 for path, src in ui.configitems('subpaths'):
39 for path, src in ui.configitems('subpaths'):
40 p.set('subpaths', path, src, ui.configsource('subpaths', path))
40 p.set('subpaths', path, src, ui.configsource('subpaths', path))
41
41
42 rev = {}
42 rev = {}
43 if '.hgsubstate' in ctx:
43 if '.hgsubstate' in ctx:
44 try:
44 try:
45 for l in ctx['.hgsubstate'].data().splitlines():
45 for l in ctx['.hgsubstate'].data().splitlines():
46 revision, path = l.split(" ", 1)
46 revision, path = l.split(" ", 1)
47 rev[path] = revision
47 rev[path] = revision
48 except IOError, err:
48 except IOError, err:
49 if err.errno != errno.ENOENT:
49 if err.errno != errno.ENOENT:
50 raise
50 raise
51
51
52 state = {}
52 state = {}
53 for path, src in p[''].items():
53 for path, src in p[''].items():
54 kind = 'hg'
54 kind = 'hg'
55 if src.startswith('['):
55 if src.startswith('['):
56 if ']' not in src:
56 if ']' not in src:
57 raise util.Abort(_('missing ] in subrepo source'))
57 raise util.Abort(_('missing ] in subrepo source'))
58 kind, src = src.split(']', 1)
58 kind, src = src.split(']', 1)
59 kind = kind[1:]
59 kind = kind[1:]
60
60
61 for pattern, repl in p.items('subpaths'):
61 for pattern, repl in p.items('subpaths'):
62 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
62 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
63 # does a string decode.
63 # does a string decode.
64 repl = repl.encode('string-escape')
64 repl = repl.encode('string-escape')
65 # However, we still want to allow back references to go
65 # However, we still want to allow back references to go
66 # through unharmed, so we turn r'\\1' into r'\1'. Again,
66 # through unharmed, so we turn r'\\1' into r'\1'. Again,
67 # extra escapes are needed because re.sub string decodes.
67 # extra escapes are needed because re.sub string decodes.
68 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
68 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
69 try:
69 try:
70 src = re.sub(pattern, repl, src, 1)
70 src = re.sub(pattern, repl, src, 1)
71 except re.error, e:
71 except re.error, e:
72 raise util.Abort(_("bad subrepository pattern in %s: %s")
72 raise util.Abort(_("bad subrepository pattern in %s: %s")
73 % (p.source('subpaths', pattern), e))
73 % (p.source('subpaths', pattern), e))
74
74
75 state[path] = (src.strip(), rev.get(path, ''), kind)
75 state[path] = (src.strip(), rev.get(path, ''), kind)
76
76
77 return state
77 return state
78
78
79 def writestate(repo, state):
79 def writestate(repo, state):
80 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
80 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
81 repo.wwrite('.hgsubstate',
81 repo.wwrite('.hgsubstate',
82 ''.join(['%s %s\n' % (state[s][1], s)
82 ''.join(['%s %s\n' % (state[s][1], s)
83 for s in sorted(state)]), '')
83 for s in sorted(state)]), '')
84
84
85 def submerge(repo, wctx, mctx, actx):
85 def submerge(repo, wctx, mctx, actx):
86 """delegated from merge.applyupdates: merging of .hgsubstate file
86 """delegated from merge.applyupdates: merging of .hgsubstate file
87 in working context, merging context and ancestor context"""
87 in working context, merging context and ancestor context"""
88 if mctx == actx: # backwards?
88 if mctx == actx: # backwards?
89 actx = wctx.p1()
89 actx = wctx.p1()
90 s1 = wctx.substate
90 s1 = wctx.substate
91 s2 = mctx.substate
91 s2 = mctx.substate
92 sa = actx.substate
92 sa = actx.substate
93 sm = {}
93 sm = {}
94
94
95 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
95 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
96
96
97 def debug(s, msg, r=""):
97 def debug(s, msg, r=""):
98 if r:
98 if r:
99 r = "%s:%s:%s" % r
99 r = "%s:%s:%s" % r
100 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
100 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
101
101
102 for s, l in s1.items():
102 for s, l in s1.items():
103 a = sa.get(s, nullstate)
103 a = sa.get(s, nullstate)
104 ld = l # local state with possible dirty flag for compares
104 ld = l # local state with possible dirty flag for compares
105 if wctx.sub(s).dirty():
105 if wctx.sub(s).dirty():
106 ld = (l[0], l[1] + "+")
106 ld = (l[0], l[1] + "+")
107 if wctx == actx: # overwrite
107 if wctx == actx: # overwrite
108 a = ld
108 a = ld
109
109
110 if s in s2:
110 if s in s2:
111 r = s2[s]
111 r = s2[s]
112 if ld == r or r == a: # no change or local is newer
112 if ld == r or r == a: # no change or local is newer
113 sm[s] = l
113 sm[s] = l
114 continue
114 continue
115 elif ld == a: # other side changed
115 elif ld == a: # other side changed
116 debug(s, "other changed, get", r)
116 debug(s, "other 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[0] != r[0]: # sources differ
119 elif ld[0] != r[0]: # sources differ
120 if repo.ui.promptchoice(
120 if repo.ui.promptchoice(
121 _(' subrepository sources for %s differ\n'
121 _(' subrepository sources for %s differ\n'
122 'use (l)ocal source (%s) or (r)emote source (%s)?')
122 'use (l)ocal source (%s) or (r)emote source (%s)?')
123 % (s, l[0], r[0]),
123 % (s, l[0], r[0]),
124 (_('&Local'), _('&Remote')), 0):
124 (_('&Local'), _('&Remote')), 0):
125 debug(s, "prompt changed, get", r)
125 debug(s, "prompt changed, get", r)
126 wctx.sub(s).get(r)
126 wctx.sub(s).get(r)
127 sm[s] = r
127 sm[s] = r
128 elif ld[1] == a[1]: # local side is unchanged
128 elif ld[1] == a[1]: # local side is unchanged
129 debug(s, "other side changed, get", r)
129 debug(s, "other side changed, get", r)
130 wctx.sub(s).get(r)
130 wctx.sub(s).get(r)
131 sm[s] = r
131 sm[s] = r
132 else:
132 else:
133 debug(s, "both sides changed, merge with", r)
133 debug(s, "both sides changed, merge with", r)
134 wctx.sub(s).merge(r)
134 wctx.sub(s).merge(r)
135 sm[s] = l
135 sm[s] = l
136 elif ld == a: # remote removed, local unchanged
136 elif ld == a: # remote removed, local unchanged
137 debug(s, "remote removed, remove")
137 debug(s, "remote removed, remove")
138 wctx.sub(s).remove()
138 wctx.sub(s).remove()
139 else:
139 else:
140 if repo.ui.promptchoice(
140 if repo.ui.promptchoice(
141 _(' local changed subrepository %s which remote removed\n'
141 _(' local changed subrepository %s which remote removed\n'
142 'use (c)hanged version or (d)elete?') % s,
142 'use (c)hanged version or (d)elete?') % s,
143 (_('&Changed'), _('&Delete')), 0):
143 (_('&Changed'), _('&Delete')), 0):
144 debug(s, "prompt remove")
144 debug(s, "prompt remove")
145 wctx.sub(s).remove()
145 wctx.sub(s).remove()
146
146
147 for s, r in s2.items():
147 for s, r in s2.items():
148 if s in s1:
148 if s in s1:
149 continue
149 continue
150 elif s not in sa:
150 elif s not in sa:
151 debug(s, "remote added, get", r)
151 debug(s, "remote added, get", r)
152 mctx.sub(s).get(r)
152 mctx.sub(s).get(r)
153 sm[s] = r
153 sm[s] = r
154 elif r != sa[s]:
154 elif r != sa[s]:
155 if repo.ui.promptchoice(
155 if repo.ui.promptchoice(
156 _(' remote changed subrepository %s which local removed\n'
156 _(' remote changed subrepository %s which local removed\n'
157 'use (c)hanged version or (d)elete?') % s,
157 'use (c)hanged version or (d)elete?') % s,
158 (_('&Changed'), _('&Delete')), 0) == 0:
158 (_('&Changed'), _('&Delete')), 0) == 0:
159 debug(s, "prompt recreate", r)
159 debug(s, "prompt recreate", r)
160 wctx.sub(s).get(r)
160 wctx.sub(s).get(r)
161 sm[s] = r
161 sm[s] = r
162
162
163 # record merged .hgsubstate
163 # record merged .hgsubstate
164 writestate(repo, sm)
164 writestate(repo, sm)
165
165
166 def reporelpath(repo):
166 def reporelpath(repo):
167 """return path to this (sub)repo as seen from outermost repo"""
167 """return path to this (sub)repo as seen from outermost repo"""
168 parent = repo
168 parent = repo
169 while hasattr(parent, '_subparent'):
169 while hasattr(parent, '_subparent'):
170 parent = parent._subparent
170 parent = parent._subparent
171 return repo.root[len(parent.root)+1:]
171 return repo.root[len(parent.root)+1:]
172
172
173 def subrelpath(sub):
173 def subrelpath(sub):
174 """return path to this subrepo as seen from outermost repo"""
174 """return path to this subrepo as seen from outermost repo"""
175 if hasattr(sub, '_relpath'):
175 if hasattr(sub, '_relpath'):
176 return sub._relpath
176 return sub._relpath
177 if not hasattr(sub, '_repo'):
177 if not hasattr(sub, '_repo'):
178 return sub._path
178 return sub._path
179 return reporelpath(sub._repo)
179 return reporelpath(sub._repo)
180
180
181 def _abssource(repo, push=False, abort=True):
181 def _abssource(repo, push=False, abort=True):
182 """return pull/push path of repo - either based on parent repo .hgsub info
182 """return pull/push path of repo - either based on parent repo .hgsub info
183 or on the top repo config. Abort or return None if no source found."""
183 or on the top repo config. Abort or return None if no source found."""
184 if hasattr(repo, '_subparent'):
184 if hasattr(repo, '_subparent'):
185 source = repo._subsource
185 source = repo._subsource
186 if source.startswith('/') or '://' in source:
186 if source.startswith('/') or '://' in source:
187 return source
187 return source
188 parent = _abssource(repo._subparent, push, abort=False)
188 parent = _abssource(repo._subparent, push, abort=False)
189 if parent:
189 if parent:
190 if '://' in parent:
190 if '://' in parent:
191 if parent[-1] == '/':
191 if parent[-1] == '/':
192 parent = parent[:-1]
192 parent = parent[:-1]
193 r = urlparse.urlparse(parent + '/' + source)
193 r = urlparse.urlparse(parent + '/' + source)
194 r = urlparse.urlunparse((r[0], r[1],
194 r = urlparse.urlunparse((r[0], r[1],
195 posixpath.normpath(r[2]),
195 posixpath.normpath(r[2]),
196 r[3], r[4], r[5]))
196 r[3], r[4], r[5]))
197 return r
197 return r
198 else: # plain file system path
198 else: # plain file system path
199 return posixpath.normpath(os.path.join(parent, repo._subsource))
199 return posixpath.normpath(os.path.join(parent, repo._subsource))
200 else: # recursion reached top repo
200 else: # recursion reached top repo
201 if hasattr(repo, '_subtoppath'):
201 if hasattr(repo, '_subtoppath'):
202 return repo._subtoppath
202 return repo._subtoppath
203 if push and repo.ui.config('paths', 'default-push'):
203 if push and repo.ui.config('paths', 'default-push'):
204 return repo.ui.config('paths', 'default-push')
204 return repo.ui.config('paths', 'default-push')
205 if repo.ui.config('paths', 'default'):
205 if repo.ui.config('paths', 'default'):
206 return repo.ui.config('paths', 'default')
206 return repo.ui.config('paths', 'default')
207 if abort:
207 if abort:
208 raise util.Abort(_("default path for subrepository %s not found") %
208 raise util.Abort(_("default path for subrepository %s not found") %
209 reporelpath(repo))
209 reporelpath(repo))
210
210
211 def itersubrepos(ctx1, ctx2):
211 def itersubrepos(ctx1, ctx2):
212 """find subrepos in ctx1 or ctx2"""
212 """find subrepos in ctx1 or ctx2"""
213 # Create a (subpath, ctx) mapping where we prefer subpaths from
213 # Create a (subpath, ctx) mapping where we prefer subpaths from
214 # ctx1. The subpaths from ctx2 are important when the .hgsub file
214 # ctx1. The subpaths from ctx2 are important when the .hgsub file
215 # has been modified (in ctx2) but not yet committed (in ctx1).
215 # has been modified (in ctx2) but not yet committed (in ctx1).
216 subpaths = dict.fromkeys(ctx2.substate, ctx2)
216 subpaths = dict.fromkeys(ctx2.substate, ctx2)
217 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
217 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
218 for subpath, ctx in sorted(subpaths.iteritems()):
218 for subpath, ctx in sorted(subpaths.iteritems()):
219 yield subpath, ctx.sub(subpath)
219 yield subpath, ctx.sub(subpath)
220
220
221 def subrepo(ctx, path):
221 def subrepo(ctx, path):
222 """return instance of the right subrepo class for subrepo in path"""
222 """return instance of the right subrepo class for subrepo in path"""
223 # subrepo inherently violates our import layering rules
223 # subrepo inherently violates our import layering rules
224 # because it wants to make repo objects from deep inside the stack
224 # because it wants to make repo objects from deep inside the stack
225 # so we manually delay the circular imports to not break
225 # so we manually delay the circular imports to not break
226 # scripts that don't use our demand-loading
226 # scripts that don't use our demand-loading
227 global hg
227 global hg
228 import hg as h
228 import hg as h
229 hg = h
229 hg = h
230
230
231 util.path_auditor(ctx._repo.root)(path)
231 util.path_auditor(ctx._repo.root)(path)
232 state = ctx.substate.get(path, nullstate)
232 state = ctx.substate.get(path, nullstate)
233 if state[2] not in types:
233 if state[2] not in types:
234 raise util.Abort(_('unknown subrepo type %s') % state[2])
234 raise util.Abort(_('unknown subrepo type %s') % state[2])
235 return types[state[2]](ctx, path, state[:2])
235 return types[state[2]](ctx, path, state[:2])
236
236
237 # subrepo classes need to implement the following abstract class:
237 # subrepo classes need to implement the following abstract class:
238
238
239 class abstractsubrepo(object):
239 class abstractsubrepo(object):
240
240
241 def dirty(self, ignoreupdate=False):
241 def dirty(self, ignoreupdate=False):
242 """returns true if the dirstate of the subrepo is dirty or does not
242 """returns true if the dirstate of the subrepo is dirty or does not
243 match current stored state. If ignoreupdate is true, only check
243 match current stored state. If ignoreupdate is true, only check
244 whether the subrepo has uncommitted changes in its dirstate.
244 whether the subrepo has uncommitted changes in its dirstate.
245 """
245 """
246 raise NotImplementedError
246 raise NotImplementedError
247
247
248 def checknested(self, path):
248 def checknested(self, path):
249 """check if path is a subrepository within this repository"""
249 """check if path is a subrepository within this repository"""
250 return False
250 return False
251
251
252 def commit(self, text, user, date):
252 def commit(self, text, user, date):
253 """commit the current changes to the subrepo with the given
253 """commit the current changes to the subrepo with the given
254 log message. Use given user and date if possible. Return the
254 log message. Use given user and date if possible. Return the
255 new state of the subrepo.
255 new state of the subrepo.
256 """
256 """
257 raise NotImplementedError
257 raise NotImplementedError
258
258
259 def remove(self):
259 def remove(self):
260 """remove the subrepo
260 """remove the subrepo
261
261
262 (should verify the dirstate is not dirty first)
262 (should verify the dirstate is not dirty first)
263 """
263 """
264 raise NotImplementedError
264 raise NotImplementedError
265
265
266 def get(self, state):
266 def get(self, state):
267 """run whatever commands are needed to put the subrepo into
267 """run whatever commands are needed to put the subrepo into
268 this state
268 this state
269 """
269 """
270 raise NotImplementedError
270 raise NotImplementedError
271
271
272 def merge(self, state):
272 def merge(self, state):
273 """merge currently-saved state with the new state."""
273 """merge currently-saved state with the new state."""
274 raise NotImplementedError
274 raise NotImplementedError
275
275
276 def push(self, force):
276 def push(self, force):
277 """perform whatever action is analogous to 'hg push'
277 """perform whatever action is analogous to 'hg push'
278
278
279 This may be a no-op on some systems.
279 This may be a no-op on some systems.
280 """
280 """
281 raise NotImplementedError
281 raise NotImplementedError
282
282
283 def add(self, ui, match, dryrun, prefix):
283 def add(self, ui, match, dryrun, prefix):
284 return []
284 return []
285
285
286 def status(self, rev2, **opts):
286 def status(self, rev2, **opts):
287 return [], [], [], [], [], [], []
287 return [], [], [], [], [], [], []
288
288
289 def diff(self, diffopts, node2, match, prefix, **opts):
289 def diff(self, diffopts, node2, match, prefix, **opts):
290 pass
290 pass
291
291
292 def outgoing(self, ui, dest, opts):
292 def outgoing(self, ui, dest, opts):
293 return 1
293 return 1
294
294
295 def incoming(self, ui, source, opts):
295 def incoming(self, ui, source, opts):
296 return 1
296 return 1
297
297
298 def files(self):
298 def files(self):
299 """return filename iterator"""
299 """return filename iterator"""
300 raise NotImplementedError
300 raise NotImplementedError
301
301
302 def filedata(self, name):
302 def filedata(self, name):
303 """return file data"""
303 """return file data"""
304 raise NotImplementedError
304 raise NotImplementedError
305
305
306 def fileflags(self, name):
306 def fileflags(self, name):
307 """return file flags"""
307 """return file flags"""
308 return ''
308 return ''
309
309
310 def archive(self, ui, archiver, prefix):
310 def archive(self, ui, archiver, prefix):
311 files = self.files()
311 files = self.files()
312 total = len(files)
312 total = len(files)
313 relpath = subrelpath(self)
313 relpath = subrelpath(self)
314 ui.progress(_('archiving (%s)') % relpath, 0,
314 ui.progress(_('archiving (%s)') % relpath, 0,
315 unit=_('files'), total=total)
315 unit=_('files'), total=total)
316 for i, name in enumerate(files):
316 for i, name in enumerate(files):
317 flags = self.fileflags(name)
317 flags = self.fileflags(name)
318 mode = 'x' in flags and 0755 or 0644
318 mode = 'x' in flags and 0755 or 0644
319 symlink = 'l' in flags
319 symlink = 'l' in flags
320 archiver.addfile(os.path.join(prefix, self._path, name),
320 archiver.addfile(os.path.join(prefix, self._path, name),
321 mode, symlink, self.filedata(name))
321 mode, symlink, self.filedata(name))
322 ui.progress(_('archiving (%s)') % relpath, i + 1,
322 ui.progress(_('archiving (%s)') % relpath, i + 1,
323 unit=_('files'), total=total)
323 unit=_('files'), total=total)
324 ui.progress(_('archiving (%s)') % relpath, None)
324 ui.progress(_('archiving (%s)') % relpath, None)
325
325
326
326
327 class hgsubrepo(abstractsubrepo):
327 class hgsubrepo(abstractsubrepo):
328 def __init__(self, ctx, path, state):
328 def __init__(self, ctx, path, state):
329 self._path = path
329 self._path = path
330 self._state = state
330 self._state = state
331 r = ctx._repo
331 r = ctx._repo
332 root = r.wjoin(path)
332 root = r.wjoin(path)
333 create = False
333 create = False
334 if not os.path.exists(os.path.join(root, '.hg')):
334 if not os.path.exists(os.path.join(root, '.hg')):
335 create = True
335 create = True
336 util.makedirs(root)
336 util.makedirs(root)
337 self._repo = hg.repository(r.ui, root, create=create)
337 self._repo = hg.repository(r.ui, root, create=create)
338 self._repo._subparent = r
338 self._repo._subparent = r
339 self._repo._subsource = state[0]
339 self._repo._subsource = state[0]
340
340
341 if create:
341 if create:
342 fp = self._repo.opener("hgrc", "w", text=True)
342 fp = self._repo.opener("hgrc", "w", text=True)
343 fp.write('[paths]\n')
343 fp.write('[paths]\n')
344
344
345 def addpathconfig(key, value):
345 def addpathconfig(key, value):
346 if value:
346 if value:
347 fp.write('%s = %s\n' % (key, value))
347 fp.write('%s = %s\n' % (key, value))
348 self._repo.ui.setconfig('paths', key, value)
348 self._repo.ui.setconfig('paths', key, value)
349
349
350 defpath = _abssource(self._repo, abort=False)
350 defpath = _abssource(self._repo, abort=False)
351 defpushpath = _abssource(self._repo, True, abort=False)
351 defpushpath = _abssource(self._repo, True, abort=False)
352 addpathconfig('default', defpath)
352 addpathconfig('default', defpath)
353 if defpath != defpushpath:
353 if defpath != defpushpath:
354 addpathconfig('default-push', defpushpath)
354 addpathconfig('default-push', defpushpath)
355 fp.close()
355 fp.close()
356
356
357 def add(self, ui, match, dryrun, prefix):
357 def add(self, ui, match, dryrun, prefix):
358 return cmdutil.add(ui, self._repo, match, dryrun, True,
358 return cmdutil.add(ui, self._repo, match, dryrun, True,
359 os.path.join(prefix, self._path))
359 os.path.join(prefix, self._path))
360
360
361 def status(self, rev2, **opts):
361 def status(self, rev2, **opts):
362 try:
362 try:
363 rev1 = self._state[1]
363 rev1 = self._state[1]
364 ctx1 = self._repo[rev1]
364 ctx1 = self._repo[rev1]
365 ctx2 = self._repo[rev2]
365 ctx2 = self._repo[rev2]
366 return self._repo.status(ctx1, ctx2, **opts)
366 return self._repo.status(ctx1, ctx2, **opts)
367 except error.RepoLookupError, inst:
367 except error.RepoLookupError, inst:
368 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
368 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
369 % (inst, subrelpath(self)))
369 % (inst, subrelpath(self)))
370 return [], [], [], [], [], [], []
370 return [], [], [], [], [], [], []
371
371
372 def diff(self, diffopts, node2, match, prefix, **opts):
372 def diff(self, diffopts, node2, match, prefix, **opts):
373 try:
373 try:
374 node1 = node.bin(self._state[1])
374 node1 = node.bin(self._state[1])
375 # We currently expect node2 to come from substate and be
375 # We currently expect node2 to come from substate and be
376 # in hex format
376 # in hex format
377 if node2 is not None:
377 if node2 is not None:
378 node2 = node.bin(node2)
378 node2 = node.bin(node2)
379 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
379 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
380 node1, node2, match,
380 node1, node2, match,
381 prefix=os.path.join(prefix, self._path),
381 prefix=os.path.join(prefix, self._path),
382 listsubrepos=True, **opts)
382 listsubrepos=True, **opts)
383 except error.RepoLookupError, inst:
383 except error.RepoLookupError, inst:
384 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
384 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
385 % (inst, subrelpath(self)))
385 % (inst, subrelpath(self)))
386
386
387 def archive(self, ui, archiver, prefix):
387 def archive(self, ui, archiver, prefix):
388 abstractsubrepo.archive(self, ui, archiver, prefix)
388 abstractsubrepo.archive(self, ui, archiver, prefix)
389
389
390 rev = self._state[1]
390 rev = self._state[1]
391 ctx = self._repo[rev]
391 ctx = self._repo[rev]
392 for subpath in ctx.substate:
392 for subpath in ctx.substate:
393 s = subrepo(ctx, subpath)
393 s = subrepo(ctx, subpath)
394 s.archive(ui, archiver, os.path.join(prefix, self._path))
394 s.archive(ui, archiver, os.path.join(prefix, self._path))
395
395
396 def dirty(self, ignoreupdate=False):
396 def dirty(self, ignoreupdate=False):
397 r = self._state[1]
397 r = self._state[1]
398 if r == '' and not ignoreupdate: # no state recorded
398 if r == '' and not ignoreupdate: # no state recorded
399 return True
399 return True
400 w = self._repo[None]
400 w = self._repo[None]
401 # version checked out changed?
401 # version checked out changed?
402 if w.p1() != self._repo[r] and not ignoreupdate:
402 if w.p1() != self._repo[r] and not ignoreupdate:
403 return True
403 return True
404 return w.dirty() # working directory changed
404 return w.dirty() # working directory changed
405
405
406 def checknested(self, path):
406 def checknested(self, path):
407 return self._repo._checknested(self._repo.wjoin(path))
407 return self._repo._checknested(self._repo.wjoin(path))
408
408
409 def commit(self, text, user, date):
409 def commit(self, text, user, date):
410 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
410 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
411 n = self._repo.commit(text, user, date)
411 n = self._repo.commit(text, user, date)
412 if not n:
412 if not n:
413 return self._repo['.'].hex() # different version checked out
413 return self._repo['.'].hex() # different version checked out
414 return node.hex(n)
414 return node.hex(n)
415
415
416 def remove(self):
416 def remove(self):
417 # we can't fully delete the repository as it may contain
417 # we can't fully delete the repository as it may contain
418 # local-only history
418 # local-only history
419 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
419 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
420 hg.clean(self._repo, node.nullid, False)
420 hg.clean(self._repo, node.nullid, False)
421
421
422 def _get(self, state):
422 def _get(self, state):
423 source, revision, kind = state
423 source, revision, kind = state
424 try:
424 try:
425 self._repo.lookup(revision)
425 self._repo.lookup(revision)
426 except error.RepoError:
426 except error.RepoError:
427 self._repo._subsource = source
427 self._repo._subsource = source
428 srcurl = _abssource(self._repo)
428 srcurl = _abssource(self._repo)
429 self._repo.ui.status(_('pulling subrepo %s from %s\n')
429 self._repo.ui.status(_('pulling subrepo %s from %s\n')
430 % (subrelpath(self), srcurl))
430 % (subrelpath(self), srcurl))
431 other = hg.repository(self._repo.ui, srcurl)
431 other = hg.repository(self._repo.ui, srcurl)
432 self._repo.pull(other)
432 self._repo.pull(other)
433
433
434 def get(self, state):
434 def get(self, state):
435 self._get(state)
435 self._get(state)
436 source, revision, kind = state
436 source, revision, kind = state
437 self._repo.ui.debug("getting subrepo %s\n" % self._path)
437 self._repo.ui.debug("getting subrepo %s\n" % self._path)
438 hg.clean(self._repo, revision, False)
438 hg.clean(self._repo, revision, False)
439
439
440 def merge(self, state):
440 def merge(self, state):
441 self._get(state)
441 self._get(state)
442 cur = self._repo['.']
442 cur = self._repo['.']
443 dst = self._repo[state[1]]
443 dst = self._repo[state[1]]
444 anc = dst.ancestor(cur)
444 anc = dst.ancestor(cur)
445 if anc == cur:
445 if anc == cur:
446 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
446 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
447 hg.update(self._repo, state[1])
447 hg.update(self._repo, state[1])
448 elif anc == dst:
448 elif anc == dst:
449 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
449 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
450 else:
450 else:
451 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
451 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
452 hg.merge(self._repo, state[1], remind=False)
452 hg.merge(self._repo, state[1], remind=False)
453
453
454 def push(self, force):
454 def push(self, force):
455 # push subrepos depth-first for coherent ordering
455 # push subrepos depth-first for coherent ordering
456 c = self._repo['']
456 c = self._repo['']
457 subs = c.substate # only repos that are committed
457 subs = c.substate # only repos that are committed
458 for s in sorted(subs):
458 for s in sorted(subs):
459 if not c.sub(s).push(force):
459 if not c.sub(s).push(force):
460 return False
460 return False
461
461
462 dsturl = _abssource(self._repo, True)
462 dsturl = _abssource(self._repo, True)
463 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
463 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
464 (subrelpath(self), dsturl))
464 (subrelpath(self), dsturl))
465 other = hg.repository(self._repo.ui, dsturl)
465 other = hg.repository(self._repo.ui, dsturl)
466 return self._repo.push(other, force)
466 return self._repo.push(other, force)
467
467
468 def outgoing(self, ui, dest, opts):
468 def outgoing(self, ui, dest, opts):
469 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
469 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
470
470
471 def incoming(self, ui, source, opts):
471 def incoming(self, ui, source, opts):
472 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
472 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
473
473
474 def files(self):
474 def files(self):
475 rev = self._state[1]
475 rev = self._state[1]
476 ctx = self._repo[rev]
476 ctx = self._repo[rev]
477 return ctx.manifest()
477 return ctx.manifest()
478
478
479 def filedata(self, name):
479 def filedata(self, name):
480 rev = self._state[1]
480 rev = self._state[1]
481 return self._repo[rev][name].data()
481 return self._repo[rev][name].data()
482
482
483 def fileflags(self, name):
483 def fileflags(self, name):
484 rev = self._state[1]
484 rev = self._state[1]
485 ctx = self._repo[rev]
485 ctx = self._repo[rev]
486 return ctx.flags(name)
486 return ctx.flags(name)
487
487
488
488
489 class svnsubrepo(abstractsubrepo):
489 class svnsubrepo(abstractsubrepo):
490 def __init__(self, ctx, path, state):
490 def __init__(self, ctx, path, state):
491 self._path = path
491 self._path = path
492 self._state = state
492 self._state = state
493 self._ctx = ctx
493 self._ctx = ctx
494 self._ui = ctx._repo.ui
494 self._ui = ctx._repo.ui
495
495
496 def _svncommand(self, commands, filename=''):
496 def _svncommand(self, commands, filename=''):
497 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
497 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
498 cmd = ['svn'] + commands + [path]
498 cmd = ['svn'] + commands + [path]
499 env = dict(os.environ)
499 env = dict(os.environ)
500 # Avoid localized output, preserve current locale for everything else.
500 # Avoid localized output, preserve current locale for everything else.
501 env['LC_MESSAGES'] = 'C'
501 env['LC_MESSAGES'] = 'C'
502 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
502 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
503 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
503 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
504 universal_newlines=True, env=env)
504 universal_newlines=True, env=env)
505 stdout, stderr = p.communicate()
505 stdout, stderr = p.communicate()
506 stderr = stderr.strip()
506 stderr = stderr.strip()
507 if stderr:
507 if stderr:
508 raise util.Abort(stderr)
508 raise util.Abort(stderr)
509 return stdout
509 return stdout
510
510
511 def _wcrev(self):
511 def _wcrev(self):
512 output = self._svncommand(['info', '--xml'])
512 output = self._svncommand(['info', '--xml'])
513 doc = xml.dom.minidom.parseString(output)
513 doc = xml.dom.minidom.parseString(output)
514 entries = doc.getElementsByTagName('entry')
514 entries = doc.getElementsByTagName('entry')
515 if not entries:
515 if not entries:
516 return '0'
516 return '0'
517 return str(entries[0].getAttribute('revision')) or '0'
517 return str(entries[0].getAttribute('revision')) or '0'
518
518
519 def _wcchanged(self):
519 def _wcchanged(self):
520 """Return (changes, extchanges) where changes is True
520 """Return (changes, extchanges) where changes is True
521 if the working directory was changed, and extchanges is
521 if the working directory was changed, and extchanges is
522 True if any of these changes concern an external entry.
522 True if any of these changes concern an external entry.
523 """
523 """
524 output = self._svncommand(['status', '--xml'])
524 output = self._svncommand(['status', '--xml'])
525 externals, changes = [], []
525 externals, changes = [], []
526 doc = xml.dom.minidom.parseString(output)
526 doc = xml.dom.minidom.parseString(output)
527 for e in doc.getElementsByTagName('entry'):
527 for e in doc.getElementsByTagName('entry'):
528 s = e.getElementsByTagName('wc-status')
528 s = e.getElementsByTagName('wc-status')
529 if not s:
529 if not s:
530 continue
530 continue
531 item = s[0].getAttribute('item')
531 item = s[0].getAttribute('item')
532 props = s[0].getAttribute('props')
532 props = s[0].getAttribute('props')
533 path = e.getAttribute('path')
533 path = e.getAttribute('path')
534 if item == 'external':
534 if item == 'external':
535 externals.append(path)
535 externals.append(path)
536 if (item not in ('', 'normal', 'unversioned', 'external')
536 if (item not in ('', 'normal', 'unversioned', 'external')
537 or props not in ('', 'none')):
537 or props not in ('', 'none')):
538 changes.append(path)
538 changes.append(path)
539 for path in changes:
539 for path in changes:
540 for ext in externals:
540 for ext in externals:
541 if path == ext or path.startswith(ext + os.sep):
541 if path == ext or path.startswith(ext + os.sep):
542 return True, True
542 return True, True
543 return bool(changes), False
543 return bool(changes), False
544
544
545 def dirty(self, ignoreupdate=False):
545 def dirty(self, ignoreupdate=False):
546 if not self._wcchanged()[0]:
546 if not self._wcchanged()[0]:
547 if self._wcrev() == self._state[1] and not ignoreupdate:
547 if self._wcrev() == self._state[1] or ignoreupdate:
548 return False
548 return False
549 return True
549 return True
550
550
551 def commit(self, text, user, date):
551 def commit(self, text, user, date):
552 # user and date are out of our hands since svn is centralized
552 # user and date are out of our hands since svn is centralized
553 changed, extchanged = self._wcchanged()
553 changed, extchanged = self._wcchanged()
554 if not changed:
554 if not changed:
555 return self._wcrev()
555 return self._wcrev()
556 if extchanged:
556 if extchanged:
557 # Do not try to commit externals
557 # Do not try to commit externals
558 raise util.Abort(_('cannot commit svn externals'))
558 raise util.Abort(_('cannot commit svn externals'))
559 commitinfo = self._svncommand(['commit', '-m', text])
559 commitinfo = self._svncommand(['commit', '-m', text])
560 self._ui.status(commitinfo)
560 self._ui.status(commitinfo)
561 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
561 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
562 if not newrev:
562 if not newrev:
563 raise util.Abort(commitinfo.splitlines()[-1])
563 raise util.Abort(commitinfo.splitlines()[-1])
564 newrev = newrev.groups()[0]
564 newrev = newrev.groups()[0]
565 self._ui.status(self._svncommand(['update', '-r', newrev]))
565 self._ui.status(self._svncommand(['update', '-r', newrev]))
566 return newrev
566 return newrev
567
567
568 def remove(self):
568 def remove(self):
569 if self.dirty():
569 if self.dirty():
570 self._ui.warn(_('not removing repo %s because '
570 self._ui.warn(_('not removing repo %s because '
571 'it has changes.\n' % self._path))
571 'it has changes.\n' % self._path))
572 return
572 return
573 self._ui.note(_('removing subrepo %s\n') % self._path)
573 self._ui.note(_('removing subrepo %s\n') % self._path)
574
574
575 def onerror(function, path, excinfo):
575 def onerror(function, path, excinfo):
576 if function is not os.remove:
576 if function is not os.remove:
577 raise
577 raise
578 # read-only files cannot be unlinked under Windows
578 # read-only files cannot be unlinked under Windows
579 s = os.stat(path)
579 s = os.stat(path)
580 if (s.st_mode & stat.S_IWRITE) != 0:
580 if (s.st_mode & stat.S_IWRITE) != 0:
581 raise
581 raise
582 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
582 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
583 os.remove(path)
583 os.remove(path)
584
584
585 path = self._ctx._repo.wjoin(self._path)
585 path = self._ctx._repo.wjoin(self._path)
586 shutil.rmtree(path, onerror=onerror)
586 shutil.rmtree(path, onerror=onerror)
587 try:
587 try:
588 os.removedirs(os.path.dirname(path))
588 os.removedirs(os.path.dirname(path))
589 except OSError:
589 except OSError:
590 pass
590 pass
591
591
592 def get(self, state):
592 def get(self, state):
593 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
593 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
594 if not re.search('Checked out revision [0-9]+.', status):
594 if not re.search('Checked out revision [0-9]+.', status):
595 raise util.Abort(status.splitlines()[-1])
595 raise util.Abort(status.splitlines()[-1])
596 self._ui.status(status)
596 self._ui.status(status)
597
597
598 def merge(self, state):
598 def merge(self, state):
599 old = int(self._state[1])
599 old = int(self._state[1])
600 new = int(state[1])
600 new = int(state[1])
601 if new > old:
601 if new > old:
602 self.get(state)
602 self.get(state)
603
603
604 def push(self, force):
604 def push(self, force):
605 # push is a no-op for SVN
605 # push is a no-op for SVN
606 return True
606 return True
607
607
608 def files(self):
608 def files(self):
609 output = self._svncommand(['list'])
609 output = self._svncommand(['list'])
610 # This works because svn forbids \n in filenames.
610 # This works because svn forbids \n in filenames.
611 return output.splitlines()
611 return output.splitlines()
612
612
613 def filedata(self, name):
613 def filedata(self, name):
614 return self._svncommand(['cat'], name)
614 return self._svncommand(['cat'], name)
615
615
616
616
617 class gitsubrepo(abstractsubrepo):
617 class gitsubrepo(abstractsubrepo):
618 def __init__(self, ctx, path, state):
618 def __init__(self, ctx, path, state):
619 # TODO add git version check.
619 # TODO add git version check.
620 self._state = state
620 self._state = state
621 self._ctx = ctx
621 self._ctx = ctx
622 self._path = path
622 self._path = path
623 self._relpath = os.path.join(reporelpath(ctx._repo), path)
623 self._relpath = os.path.join(reporelpath(ctx._repo), path)
624 self._abspath = ctx._repo.wjoin(path)
624 self._abspath = ctx._repo.wjoin(path)
625 self._ui = ctx._repo.ui
625 self._ui = ctx._repo.ui
626
626
627 def _gitcommand(self, commands, env=None, stream=False):
627 def _gitcommand(self, commands, env=None, stream=False):
628 return self._gitdir(commands, env=env, stream=stream)[0]
628 return self._gitdir(commands, env=env, stream=stream)[0]
629
629
630 def _gitdir(self, commands, env=None, stream=False):
630 def _gitdir(self, commands, env=None, stream=False):
631 return self._gitnodir(commands, env=env, stream=stream,
631 return self._gitnodir(commands, env=env, stream=stream,
632 cwd=self._abspath)
632 cwd=self._abspath)
633
633
634 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
634 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
635 """Calls the git command
635 """Calls the git command
636
636
637 The methods tries to call the git command. versions previor to 1.6.0
637 The methods tries to call the git command. versions previor to 1.6.0
638 are not supported and very probably fail.
638 are not supported and very probably fail.
639 """
639 """
640 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
640 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
641 # unless ui.quiet is set, print git's stderr,
641 # unless ui.quiet is set, print git's stderr,
642 # which is mostly progress and useful info
642 # which is mostly progress and useful info
643 errpipe = None
643 errpipe = None
644 if self._ui.quiet:
644 if self._ui.quiet:
645 errpipe = open(os.devnull, 'w')
645 errpipe = open(os.devnull, 'w')
646 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
646 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
647 close_fds=util.closefds,
647 close_fds=util.closefds,
648 stdout=subprocess.PIPE, stderr=errpipe)
648 stdout=subprocess.PIPE, stderr=errpipe)
649 if stream:
649 if stream:
650 return p.stdout, None
650 return p.stdout, None
651
651
652 retdata = p.stdout.read().strip()
652 retdata = p.stdout.read().strip()
653 # wait for the child to exit to avoid race condition.
653 # wait for the child to exit to avoid race condition.
654 p.wait()
654 p.wait()
655
655
656 if p.returncode != 0 and p.returncode != 1:
656 if p.returncode != 0 and p.returncode != 1:
657 # there are certain error codes that are ok
657 # there are certain error codes that are ok
658 command = commands[0]
658 command = commands[0]
659 if command in ('cat-file', 'symbolic-ref'):
659 if command in ('cat-file', 'symbolic-ref'):
660 return retdata, p.returncode
660 return retdata, p.returncode
661 # for all others, abort
661 # for all others, abort
662 raise util.Abort('git %s error %d in %s' %
662 raise util.Abort('git %s error %d in %s' %
663 (command, p.returncode, self._relpath))
663 (command, p.returncode, self._relpath))
664
664
665 return retdata, p.returncode
665 return retdata, p.returncode
666
666
667 def _gitstate(self):
667 def _gitstate(self):
668 return self._gitcommand(['rev-parse', 'HEAD'])
668 return self._gitcommand(['rev-parse', 'HEAD'])
669
669
670 def _gitcurrentbranch(self):
670 def _gitcurrentbranch(self):
671 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
671 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
672 if err:
672 if err:
673 current = None
673 current = None
674 return current
674 return current
675
675
676 def _githavelocally(self, revision):
676 def _githavelocally(self, revision):
677 out, code = self._gitdir(['cat-file', '-e', revision])
677 out, code = self._gitdir(['cat-file', '-e', revision])
678 return code == 0
678 return code == 0
679
679
680 def _gitisancestor(self, r1, r2):
680 def _gitisancestor(self, r1, r2):
681 base = self._gitcommand(['merge-base', r1, r2])
681 base = self._gitcommand(['merge-base', r1, r2])
682 return base == r1
682 return base == r1
683
683
684 def _gitbranchmap(self):
684 def _gitbranchmap(self):
685 '''returns 2 things:
685 '''returns 2 things:
686 a map from git branch to revision
686 a map from git branch to revision
687 a map from revision to branches'''
687 a map from revision to branches'''
688 branch2rev = {}
688 branch2rev = {}
689 rev2branch = {}
689 rev2branch = {}
690
690
691 out = self._gitcommand(['for-each-ref', '--format',
691 out = self._gitcommand(['for-each-ref', '--format',
692 '%(objectname) %(refname)'])
692 '%(objectname) %(refname)'])
693 for line in out.split('\n'):
693 for line in out.split('\n'):
694 revision, ref = line.split(' ')
694 revision, ref = line.split(' ')
695 if ref.startswith('refs/tags/'):
695 if ref.startswith('refs/tags/'):
696 continue
696 continue
697 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
697 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
698 continue # ignore remote/HEAD redirects
698 continue # ignore remote/HEAD redirects
699 branch2rev[ref] = revision
699 branch2rev[ref] = revision
700 rev2branch.setdefault(revision, []).append(ref)
700 rev2branch.setdefault(revision, []).append(ref)
701 return branch2rev, rev2branch
701 return branch2rev, rev2branch
702
702
703 def _gittracking(self, branches):
703 def _gittracking(self, branches):
704 'return map of remote branch to local tracking branch'
704 'return map of remote branch to local tracking branch'
705 # assumes no more than one local tracking branch for each remote
705 # assumes no more than one local tracking branch for each remote
706 tracking = {}
706 tracking = {}
707 for b in branches:
707 for b in branches:
708 if b.startswith('refs/remotes/'):
708 if b.startswith('refs/remotes/'):
709 continue
709 continue
710 remote = self._gitcommand(['config', 'branch.%s.remote' % b])
710 remote = self._gitcommand(['config', 'branch.%s.remote' % b])
711 if remote:
711 if remote:
712 ref = self._gitcommand(['config', 'branch.%s.merge' % b])
712 ref = self._gitcommand(['config', 'branch.%s.merge' % b])
713 tracking['refs/remotes/%s/%s' %
713 tracking['refs/remotes/%s/%s' %
714 (remote, ref.split('/', 2)[2])] = b
714 (remote, ref.split('/', 2)[2])] = b
715 return tracking
715 return tracking
716
716
717 def _fetch(self, source, revision):
717 def _fetch(self, source, revision):
718 if not os.path.exists(os.path.join(self._abspath, '.git')):
718 if not os.path.exists(os.path.join(self._abspath, '.git')):
719 self._ui.status(_('cloning subrepo %s\n') % self._relpath)
719 self._ui.status(_('cloning subrepo %s\n') % self._relpath)
720 self._gitnodir(['clone', source, self._abspath])
720 self._gitnodir(['clone', source, self._abspath])
721 if self._githavelocally(revision):
721 if self._githavelocally(revision):
722 return
722 return
723 self._ui.status(_('pulling subrepo %s\n') % self._relpath)
723 self._ui.status(_('pulling subrepo %s\n') % self._relpath)
724 # first try from origin
724 # first try from origin
725 self._gitcommand(['fetch'])
725 self._gitcommand(['fetch'])
726 if self._githavelocally(revision):
726 if self._githavelocally(revision):
727 return
727 return
728 # then try from known subrepo source
728 # then try from known subrepo source
729 self._gitcommand(['fetch', source])
729 self._gitcommand(['fetch', source])
730 if not self._githavelocally(revision):
730 if not self._githavelocally(revision):
731 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
731 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
732 (revision, self._relpath))
732 (revision, self._relpath))
733
733
734 def dirty(self, ignoreupdate=False):
734 def dirty(self, ignoreupdate=False):
735 # version checked out changed?
735 # version checked out changed?
736 if not ignoreupdate and self._state[1] != self._gitstate():
736 if not ignoreupdate and self._state[1] != self._gitstate():
737 return True
737 return True
738 # check for staged changes or modified files; ignore untracked files
738 # check for staged changes or modified files; ignore untracked files
739 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
739 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
740 return code == 1
740 return code == 1
741
741
742 def get(self, state):
742 def get(self, state):
743 source, revision, kind = state
743 source, revision, kind = state
744 self._fetch(source, revision)
744 self._fetch(source, revision)
745 # if the repo was set to be bare, unbare it
745 # if the repo was set to be bare, unbare it
746 if self._gitcommand(['config', '--bool', 'core.bare']) == 'true':
746 if self._gitcommand(['config', '--bool', 'core.bare']) == 'true':
747 self._gitcommand(['config', 'core.bare', 'false'])
747 self._gitcommand(['config', 'core.bare', 'false'])
748 if self._gitstate() == revision:
748 if self._gitstate() == revision:
749 self._gitcommand(['reset', '--hard', 'HEAD'])
749 self._gitcommand(['reset', '--hard', 'HEAD'])
750 return
750 return
751 elif self._gitstate() == revision:
751 elif self._gitstate() == revision:
752 return
752 return
753 branch2rev, rev2branch = self._gitbranchmap()
753 branch2rev, rev2branch = self._gitbranchmap()
754
754
755 def rawcheckout():
755 def rawcheckout():
756 # no branch to checkout, check it out with no branch
756 # no branch to checkout, check it out with no branch
757 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
757 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
758 self._relpath)
758 self._relpath)
759 self._ui.warn(_('check out a git branch if you intend '
759 self._ui.warn(_('check out a git branch if you intend '
760 'to make changes\n'))
760 'to make changes\n'))
761 self._gitcommand(['checkout', '-q', revision])
761 self._gitcommand(['checkout', '-q', revision])
762
762
763 if revision not in rev2branch:
763 if revision not in rev2branch:
764 rawcheckout()
764 rawcheckout()
765 return
765 return
766 branches = rev2branch[revision]
766 branches = rev2branch[revision]
767 firstlocalbranch = None
767 firstlocalbranch = None
768 for b in branches:
768 for b in branches:
769 if b == 'refs/heads/master':
769 if b == 'refs/heads/master':
770 # master trumps all other branches
770 # master trumps all other branches
771 self._gitcommand(['checkout', 'refs/heads/master'])
771 self._gitcommand(['checkout', 'refs/heads/master'])
772 return
772 return
773 if not firstlocalbranch and not b.startswith('refs/remotes/'):
773 if not firstlocalbranch and not b.startswith('refs/remotes/'):
774 firstlocalbranch = b
774 firstlocalbranch = b
775 if firstlocalbranch:
775 if firstlocalbranch:
776 self._gitcommand(['checkout', firstlocalbranch])
776 self._gitcommand(['checkout', firstlocalbranch])
777 return
777 return
778
778
779 tracking = self._gittracking(branch2rev.keys())
779 tracking = self._gittracking(branch2rev.keys())
780 # choose a remote branch already tracked if possible
780 # choose a remote branch already tracked if possible
781 remote = branches[0]
781 remote = branches[0]
782 if remote not in tracking:
782 if remote not in tracking:
783 for b in branches:
783 for b in branches:
784 if b in tracking:
784 if b in tracking:
785 remote = b
785 remote = b
786 break
786 break
787
787
788 if remote not in tracking:
788 if remote not in tracking:
789 # create a new local tracking branch
789 # create a new local tracking branch
790 local = remote.split('/', 2)[2]
790 local = remote.split('/', 2)[2]
791 self._gitcommand(['checkout', '-b', local, remote])
791 self._gitcommand(['checkout', '-b', local, remote])
792 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
792 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
793 # When updating to a tracked remote branch,
793 # When updating to a tracked remote branch,
794 # if the local tracking branch is downstream of it,
794 # if the local tracking branch is downstream of it,
795 # a normal `git pull` would have performed a "fast-forward merge"
795 # a normal `git pull` would have performed a "fast-forward merge"
796 # which is equivalent to updating the local branch to the remote.
796 # which is equivalent to updating the local branch to the remote.
797 # Since we are only looking at branching at update, we need to
797 # Since we are only looking at branching at update, we need to
798 # detect this situation and perform this action lazily.
798 # detect this situation and perform this action lazily.
799 if tracking[remote] != self._gitcurrentbranch():
799 if tracking[remote] != self._gitcurrentbranch():
800 self._gitcommand(['checkout', tracking[remote]])
800 self._gitcommand(['checkout', tracking[remote]])
801 self._gitcommand(['merge', '--ff', remote])
801 self._gitcommand(['merge', '--ff', remote])
802 else:
802 else:
803 # a real merge would be required, just checkout the revision
803 # a real merge would be required, just checkout the revision
804 rawcheckout()
804 rawcheckout()
805
805
806 def commit(self, text, user, date):
806 def commit(self, text, user, date):
807 cmd = ['commit', '-a', '-m', text]
807 cmd = ['commit', '-a', '-m', text]
808 env = os.environ.copy()
808 env = os.environ.copy()
809 if user:
809 if user:
810 cmd += ['--author', user]
810 cmd += ['--author', user]
811 if date:
811 if date:
812 # git's date parser silently ignores when seconds < 1e9
812 # git's date parser silently ignores when seconds < 1e9
813 # convert to ISO8601
813 # convert to ISO8601
814 env['GIT_AUTHOR_DATE'] = util.datestr(date,
814 env['GIT_AUTHOR_DATE'] = util.datestr(date,
815 '%Y-%m-%dT%H:%M:%S %1%2')
815 '%Y-%m-%dT%H:%M:%S %1%2')
816 self._gitcommand(cmd, env=env)
816 self._gitcommand(cmd, env=env)
817 # make sure commit works otherwise HEAD might not exist under certain
817 # make sure commit works otherwise HEAD might not exist under certain
818 # circumstances
818 # circumstances
819 return self._gitstate()
819 return self._gitstate()
820
820
821 def merge(self, state):
821 def merge(self, state):
822 source, revision, kind = state
822 source, revision, kind = state
823 self._fetch(source, revision)
823 self._fetch(source, revision)
824 base = self._gitcommand(['merge-base', revision, self._state[1]])
824 base = self._gitcommand(['merge-base', revision, self._state[1]])
825 if base == revision:
825 if base == revision:
826 self.get(state) # fast forward merge
826 self.get(state) # fast forward merge
827 elif base != self._state[1]:
827 elif base != self._state[1]:
828 self._gitcommand(['merge', '--no-commit', revision])
828 self._gitcommand(['merge', '--no-commit', revision])
829
829
830 def push(self, force):
830 def push(self, force):
831 # if a branch in origin contains the revision, nothing to do
831 # if a branch in origin contains the revision, nothing to do
832 branch2rev, rev2branch = self._gitbranchmap()
832 branch2rev, rev2branch = self._gitbranchmap()
833 if self._state[1] in rev2branch:
833 if self._state[1] in rev2branch:
834 for b in rev2branch[self._state[1]]:
834 for b in rev2branch[self._state[1]]:
835 if b.startswith('refs/remotes/origin/'):
835 if b.startswith('refs/remotes/origin/'):
836 return True
836 return True
837 for b, revision in branch2rev.iteritems():
837 for b, revision in branch2rev.iteritems():
838 if b.startswith('refs/remotes/origin/'):
838 if b.startswith('refs/remotes/origin/'):
839 if self._gitisancestor(self._state[1], revision):
839 if self._gitisancestor(self._state[1], revision):
840 return True
840 return True
841 # otherwise, try to push the currently checked out branch
841 # otherwise, try to push the currently checked out branch
842 cmd = ['push']
842 cmd = ['push']
843 if force:
843 if force:
844 cmd.append('--force')
844 cmd.append('--force')
845
845
846 current = self._gitcurrentbranch()
846 current = self._gitcurrentbranch()
847 if current:
847 if current:
848 # determine if the current branch is even useful
848 # determine if the current branch is even useful
849 if not self._gitisancestor(self._state[1], current):
849 if not self._gitisancestor(self._state[1], current):
850 self._ui.warn(_('unrelated git branch checked out '
850 self._ui.warn(_('unrelated git branch checked out '
851 'in subrepo %s\n') % self._relpath)
851 'in subrepo %s\n') % self._relpath)
852 return False
852 return False
853 self._ui.status(_('pushing branch %s of subrepo %s\n') %
853 self._ui.status(_('pushing branch %s of subrepo %s\n') %
854 (current.split('/', 2)[2], self._relpath))
854 (current.split('/', 2)[2], self._relpath))
855 self._gitcommand(cmd + ['origin', current])
855 self._gitcommand(cmd + ['origin', current])
856 return True
856 return True
857 else:
857 else:
858 self._ui.warn(_('no branch checked out in subrepo %s\n'
858 self._ui.warn(_('no branch checked out in subrepo %s\n'
859 'cannot push revision %s') %
859 'cannot push revision %s') %
860 (self._relpath, self._state[1]))
860 (self._relpath, self._state[1]))
861 return False
861 return False
862
862
863 def remove(self):
863 def remove(self):
864 if self.dirty():
864 if self.dirty():
865 self._ui.warn(_('not removing repo %s because '
865 self._ui.warn(_('not removing repo %s because '
866 'it has changes.\n') % self._relpath)
866 'it has changes.\n') % self._relpath)
867 return
867 return
868 # we can't fully delete the repository as it may contain
868 # we can't fully delete the repository as it may contain
869 # local-only history
869 # local-only history
870 self._ui.note(_('removing subrepo %s\n') % self._relpath)
870 self._ui.note(_('removing subrepo %s\n') % self._relpath)
871 self._gitcommand(['config', 'core.bare', 'true'])
871 self._gitcommand(['config', 'core.bare', 'true'])
872 for f in os.listdir(self._abspath):
872 for f in os.listdir(self._abspath):
873 if f == '.git':
873 if f == '.git':
874 continue
874 continue
875 path = os.path.join(self._abspath, f)
875 path = os.path.join(self._abspath, f)
876 if os.path.isdir(path) and not os.path.islink(path):
876 if os.path.isdir(path) and not os.path.islink(path):
877 shutil.rmtree(path)
877 shutil.rmtree(path)
878 else:
878 else:
879 os.remove(path)
879 os.remove(path)
880
880
881 def archive(self, ui, archiver, prefix):
881 def archive(self, ui, archiver, prefix):
882 source, revision = self._state
882 source, revision = self._state
883 self._fetch(source, revision)
883 self._fetch(source, revision)
884
884
885 # Parse git's native archive command.
885 # Parse git's native archive command.
886 # This should be much faster than manually traversing the trees
886 # This should be much faster than manually traversing the trees
887 # and objects with many subprocess calls.
887 # and objects with many subprocess calls.
888 tarstream = self._gitcommand(['archive', revision], stream=True)
888 tarstream = self._gitcommand(['archive', revision], stream=True)
889 tar = tarfile.open(fileobj=tarstream, mode='r|')
889 tar = tarfile.open(fileobj=tarstream, mode='r|')
890 relpath = subrelpath(self)
890 relpath = subrelpath(self)
891 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
891 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
892 for i, info in enumerate(tar):
892 for i, info in enumerate(tar):
893 if info.isdir():
893 if info.isdir():
894 continue
894 continue
895 if info.issym():
895 if info.issym():
896 data = info.linkname
896 data = info.linkname
897 else:
897 else:
898 data = tar.extractfile(info).read()
898 data = tar.extractfile(info).read()
899 archiver.addfile(os.path.join(prefix, self._path, info.name),
899 archiver.addfile(os.path.join(prefix, self._path, info.name),
900 info.mode, info.issym(), data)
900 info.mode, info.issym(), data)
901 ui.progress(_('archiving (%s)') % relpath, i + 1,
901 ui.progress(_('archiving (%s)') % relpath, i + 1,
902 unit=_('files'))
902 unit=_('files'))
903 ui.progress(_('archiving (%s)') % relpath, None)
903 ui.progress(_('archiving (%s)') % relpath, None)
904
904
905
905
906 def status(self, rev2, **opts):
906 def status(self, rev2, **opts):
907 rev1 = self._state[1]
907 rev1 = self._state[1]
908 modified, added, removed = [], [], []
908 modified, added, removed = [], [], []
909 if rev2:
909 if rev2:
910 command = ['diff-tree', rev1, rev2]
910 command = ['diff-tree', rev1, rev2]
911 else:
911 else:
912 command = ['diff-index', rev1]
912 command = ['diff-index', rev1]
913 out = self._gitcommand(command)
913 out = self._gitcommand(command)
914 for line in out.split('\n'):
914 for line in out.split('\n'):
915 tab = line.find('\t')
915 tab = line.find('\t')
916 if tab == -1:
916 if tab == -1:
917 continue
917 continue
918 status, f = line[tab - 1], line[tab + 1:]
918 status, f = line[tab - 1], line[tab + 1:]
919 if status == 'M':
919 if status == 'M':
920 modified.append(f)
920 modified.append(f)
921 elif status == 'A':
921 elif status == 'A':
922 added.append(f)
922 added.append(f)
923 elif status == 'D':
923 elif status == 'D':
924 removed.append(f)
924 removed.append(f)
925
925
926 deleted = unknown = ignored = clean = []
926 deleted = unknown = ignored = clean = []
927 return modified, added, removed, deleted, unknown, ignored, clean
927 return modified, added, removed, deleted, unknown, ignored, clean
928
928
929 types = {
929 types = {
930 'hg': hgsubrepo,
930 'hg': hgsubrepo,
931 'svn': svnsubrepo,
931 'svn': svnsubrepo,
932 'git': gitsubrepo,
932 'git': gitsubrepo,
933 }
933 }
@@ -1,347 +1,386 b''
1 $ echo "[extensions]" >> $HGRCPATH
1 $ echo "[extensions]" >> $HGRCPATH
2 $ echo "mq=" >> $HGRCPATH
2 $ echo "mq=" >> $HGRCPATH
3 $ echo "record=" >> $HGRCPATH
3 $ echo "record=" >> $HGRCPATH
4 $ echo "[diff]" >> $HGRCPATH
4 $ echo "[diff]" >> $HGRCPATH
5 $ echo "nodates=1" >> $HGRCPATH
5 $ echo "nodates=1" >> $HGRCPATH
6
6
7 $ stdin=`pwd`/stdin.tmp
7 $ stdin=`pwd`/stdin.tmp
8
8
9 fn to create new repository w/dirty subrepo, and cd into it
9 fn to create new repository w/dirty subrepo, and cd into it
10 $ mkrepo() {
10 $ mkrepo() {
11 > hg init $1
11 > hg init $1
12 > cd $1
12 > cd $1
13 > hg qinit
13 > hg qinit
14 > }
14 > }
15
15
16 fn to create dirty subrepo
16 fn to create dirty subrepo
17 $ mksubrepo() {
17 $ mksubrepo() {
18 > hg init $1
18 > hg init $1
19 > cd $1
19 > cd $1
20 > echo a > a
20 > echo a > a
21 > hg add
21 > hg add
22 > cd ..
22 > cd ..
23 > }
23 > }
24
24
25 $ testadd() {
25 $ testadd() {
26 > cat - > "$stdin"
26 > cat - > "$stdin"
27 > mksubrepo sub
27 > mksubrepo sub
28 > echo sub = sub >> .hgsub
28 > echo sub = sub >> .hgsub
29 > hg add .hgsub
29 > hg add .hgsub
30 > echo % abort when adding .hgsub w/dirty subrepo
30 > echo % abort when adding .hgsub w/dirty subrepo
31 > hg status -S
31 > hg status -S
32 > echo '%' $*
32 > echo '%' $*
33 > cat "$stdin" | hg $*
33 > cat "$stdin" | hg $*
34 > echo [$?]
34 > echo [$?]
35 > hg -R sub ci -m0sub
35 > hg -R sub ci -m0sub
36 > echo % update substate when adding .hgsub w/clean updated subrepo
36 > echo % update substate when adding .hgsub w/clean updated subrepo
37 > hg status -S
37 > hg status -S
38 > echo '%' $*
38 > echo '%' $*
39 > cat "$stdin" | hg $*
39 > cat "$stdin" | hg $*
40 > hg debugsub
40 > hg debugsub
41 > }
41 > }
42
42
43 $ testmod() {
43 $ testmod() {
44 > cat - > "$stdin"
44 > cat - > "$stdin"
45 > mksubrepo sub2
45 > mksubrepo sub2
46 > echo sub2 = sub2 >> .hgsub
46 > echo sub2 = sub2 >> .hgsub
47 > echo % abort when modifying .hgsub w/dirty subrepo
47 > echo % abort when modifying .hgsub w/dirty subrepo
48 > hg status -S
48 > hg status -S
49 > echo '%' $*
49 > echo '%' $*
50 > cat "$stdin" | hg $*
50 > cat "$stdin" | hg $*
51 > echo [$?]
51 > echo [$?]
52 > hg -R sub2 ci -m0sub2
52 > hg -R sub2 ci -m0sub2
53 > echo % update substate when modifying .hgsub w/clean updated subrepo
53 > echo % update substate when modifying .hgsub w/clean updated subrepo
54 > hg status -S
54 > hg status -S
55 > echo '%' $*
55 > echo '%' $*
56 > cat "$stdin" | hg $*
56 > cat "$stdin" | hg $*
57 > hg debugsub
57 > hg debugsub
58 > }
58 > }
59
59
60 $ testrm1() {
60 $ testrm1() {
61 > cat - > "$stdin"
61 > cat - > "$stdin"
62 > mksubrepo sub3
62 > mksubrepo sub3
63 > echo sub3 = sub3 >> .hgsub
63 > echo sub3 = sub3 >> .hgsub
64 > hg ci -Aqmsub3
64 > hg ci -Aqmsub3
65 > $EXTRA
65 > $EXTRA
66 > echo b >> sub3/a
66 > echo b >> sub3/a
67 > hg rm .hgsub
67 > hg rm .hgsub
68 > echo % update substate when removing .hgsub w/dirty subrepo
68 > echo % update substate when removing .hgsub w/dirty subrepo
69 > hg status -S
69 > hg status -S
70 > echo '%' $*
70 > echo '%' $*
71 > cat "$stdin" | hg $*
71 > cat "$stdin" | hg $*
72 > echo % debugsub should be empty
72 > echo % debugsub should be empty
73 > hg debugsub
73 > hg debugsub
74 > }
74 > }
75
75
76 $ testrm2() {
76 $ testrm2() {
77 > cat - > "$stdin"
77 > cat - > "$stdin"
78 > mksubrepo sub4
78 > mksubrepo sub4
79 > echo sub4 = sub4 >> .hgsub
79 > echo sub4 = sub4 >> .hgsub
80 > hg ci -Aqmsub4
80 > hg ci -Aqmsub4
81 > $EXTRA
81 > $EXTRA
82 > hg rm .hgsub
82 > hg rm .hgsub
83 > echo % update substate when removing .hgsub w/clean updated subrepo
83 > echo % update substate when removing .hgsub w/clean updated subrepo
84 > hg status -S
84 > hg status -S
85 > echo '%' $*
85 > echo '%' $*
86 > cat "$stdin" | hg $*
86 > cat "$stdin" | hg $*
87 > echo % debugsub should be empty
87 > echo % debugsub should be empty
88 > hg debugsub
88 > hg debugsub
89 > }
89 > }
90
90
91
91
92 handle subrepos safely on qnew
92 handle subrepos safely on qnew
93
93
94 $ mkrepo repo-2499-qnew
94 $ mkrepo repo-2499-qnew
95 $ testadd qnew -m0 0.diff
95 $ testadd qnew -m0 0.diff
96 adding a
96 adding a
97 % abort when adding .hgsub w/dirty subrepo
97 % abort when adding .hgsub w/dirty subrepo
98 A .hgsub
98 A .hgsub
99 A sub/a
99 A sub/a
100 % qnew -m0 0.diff
100 % qnew -m0 0.diff
101 abort: uncommitted changes in subrepository sub
101 abort: uncommitted changes in subrepository sub
102 [255]
102 [255]
103 % update substate when adding .hgsub w/clean updated subrepo
103 % update substate when adding .hgsub w/clean updated subrepo
104 A .hgsub
104 A .hgsub
105 % qnew -m0 0.diff
105 % qnew -m0 0.diff
106 committing subrepository sub
106 committing subrepository sub
107 path sub
107 path sub
108 source sub
108 source sub
109 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
109 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
110
110
111 $ testmod qnew -m1 1.diff
111 $ testmod qnew -m1 1.diff
112 adding a
112 adding a
113 % abort when modifying .hgsub w/dirty subrepo
113 % abort when modifying .hgsub w/dirty subrepo
114 M .hgsub
114 M .hgsub
115 A sub2/a
115 A sub2/a
116 % qnew -m1 1.diff
116 % qnew -m1 1.diff
117 abort: uncommitted changes in subrepository sub2
117 abort: uncommitted changes in subrepository sub2
118 [255]
118 [255]
119 % update substate when modifying .hgsub w/clean updated subrepo
119 % update substate when modifying .hgsub w/clean updated subrepo
120 M .hgsub
120 M .hgsub
121 % qnew -m1 1.diff
121 % qnew -m1 1.diff
122 committing subrepository sub2
122 committing subrepository sub2
123 path sub
123 path sub
124 source sub
124 source sub
125 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
125 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
126 path sub2
126 path sub2
127 source sub2
127 source sub2
128 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
128 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
129
129
130 $ hg qpop -qa
130 $ hg qpop -qa
131 patch queue now empty
131 patch queue now empty
132 $ testrm1 qnew -m2 2.diff
132 $ testrm1 qnew -m2 2.diff
133 adding a
133 adding a
134 % update substate when removing .hgsub w/dirty subrepo
134 % update substate when removing .hgsub w/dirty subrepo
135 M sub3/a
135 M sub3/a
136 R .hgsub
136 R .hgsub
137 % qnew -m2 2.diff
137 % qnew -m2 2.diff
138 % debugsub should be empty
138 % debugsub should be empty
139
139
140 $ hg qpop -qa
140 $ hg qpop -qa
141 patch queue now empty
141 patch queue now empty
142 $ testrm2 qnew -m3 3.diff
142 $ testrm2 qnew -m3 3.diff
143 adding a
143 adding a
144 % update substate when removing .hgsub w/clean updated subrepo
144 % update substate when removing .hgsub w/clean updated subrepo
145 R .hgsub
145 R .hgsub
146 % qnew -m3 3.diff
146 % qnew -m3 3.diff
147 % debugsub should be empty
147 % debugsub should be empty
148
148
149 $ cd ..
149 $ cd ..
150
150
151
151
152 handle subrepos safely on qrefresh
152 handle subrepos safely on qrefresh
153
153
154 $ mkrepo repo-2499-qrefresh
154 $ mkrepo repo-2499-qrefresh
155 $ hg qnew -m0 0.diff
155 $ hg qnew -m0 0.diff
156 $ testadd qrefresh
156 $ testadd qrefresh
157 adding a
157 adding a
158 % abort when adding .hgsub w/dirty subrepo
158 % abort when adding .hgsub w/dirty subrepo
159 A .hgsub
159 A .hgsub
160 A sub/a
160 A sub/a
161 % qrefresh
161 % qrefresh
162 abort: uncommitted changes in subrepository sub
162 abort: uncommitted changes in subrepository sub
163 [255]
163 [255]
164 % update substate when adding .hgsub w/clean updated subrepo
164 % update substate when adding .hgsub w/clean updated subrepo
165 A .hgsub
165 A .hgsub
166 % qrefresh
166 % qrefresh
167 committing subrepository sub
167 committing subrepository sub
168 path sub
168 path sub
169 source sub
169 source sub
170 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
170 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
171
171
172 $ hg qnew -m1 1.diff
172 $ hg qnew -m1 1.diff
173 $ testmod qrefresh
173 $ testmod qrefresh
174 adding a
174 adding a
175 % abort when modifying .hgsub w/dirty subrepo
175 % abort when modifying .hgsub w/dirty subrepo
176 M .hgsub
176 M .hgsub
177 A sub2/a
177 A sub2/a
178 % qrefresh
178 % qrefresh
179 abort: uncommitted changes in subrepository sub2
179 abort: uncommitted changes in subrepository sub2
180 [255]
180 [255]
181 % update substate when modifying .hgsub w/clean updated subrepo
181 % update substate when modifying .hgsub w/clean updated subrepo
182 M .hgsub
182 M .hgsub
183 % qrefresh
183 % qrefresh
184 committing subrepository sub2
184 committing subrepository sub2
185 path sub
185 path sub
186 source sub
186 source sub
187 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
187 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
188 path sub2
188 path sub2
189 source sub2
189 source sub2
190 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
190 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
191
191
192 $ hg qpop -qa
192 $ hg qpop -qa
193 patch queue now empty
193 patch queue now empty
194 $ EXTRA='hg qnew -m2 2.diff' testrm1 qrefresh
194 $ EXTRA='hg qnew -m2 2.diff' testrm1 qrefresh
195 adding a
195 adding a
196 % update substate when removing .hgsub w/dirty subrepo
196 % update substate when removing .hgsub w/dirty subrepo
197 M sub3/a
197 M sub3/a
198 R .hgsub
198 R .hgsub
199 % qrefresh
199 % qrefresh
200 % debugsub should be empty
200 % debugsub should be empty
201
201
202 $ hg qpop -qa
202 $ hg qpop -qa
203 patch queue now empty
203 patch queue now empty
204 $ EXTRA='hg qnew -m3 3.diff' testrm2 qrefresh
204 $ EXTRA='hg qnew -m3 3.diff' testrm2 qrefresh
205 adding a
205 adding a
206 % update substate when removing .hgsub w/clean updated subrepo
206 % update substate when removing .hgsub w/clean updated subrepo
207 R .hgsub
207 R .hgsub
208 % qrefresh
208 % qrefresh
209 % debugsub should be empty
209 % debugsub should be empty
210
210
211 $ cd ..
211 $ cd ..
212
212
213
213
214 handle subrepos safely on qpush/qpop
214 handle subrepos safely on qpush/qpop
215
215
216 $ mkrepo repo-2499-qpush
216 $ mkrepo repo-2499-qpush
217 $ mksubrepo sub
217 $ mksubrepo sub
218 adding a
218 adding a
219 $ hg -R sub ci -m0sub
219 $ hg -R sub ci -m0sub
220 $ echo sub = sub > .hgsub
220 $ echo sub = sub > .hgsub
221 $ hg add .hgsub
221 $ hg add .hgsub
222 $ hg qnew -m0 0.diff
222 $ hg qnew -m0 0.diff
223 committing subrepository sub
223 committing subrepository sub
224 $ hg debugsub
224 $ hg debugsub
225 path sub
225 path sub
226 source sub
226 source sub
227 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
227 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
228
228
229 qpop
229 qpop
230 $ hg qpop
230 $ hg qpop
231 popping 0.diff
231 popping 0.diff
232 patch queue now empty
232 patch queue now empty
233 $ hg status -AS
233 $ hg status -AS
234 $ hg debugsub
234 $ hg debugsub
235
235
236 qpush
236 qpush
237 $ hg qpush
237 $ hg qpush
238 applying 0.diff
238 applying 0.diff
239 now at: 0.diff
239 now at: 0.diff
240 $ hg status -AS
240 $ hg status -AS
241 C .hgsub
241 C .hgsub
242 C .hgsubstate
242 C .hgsubstate
243 C sub/a
243 C sub/a
244 $ hg debugsub
244 $ hg debugsub
245 path sub
245 path sub
246 source sub
246 source sub
247 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
247 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
248
248
249 $ cd ..
249 $ cd ..
250
250
251
251
252 handle subrepos safely on qrecord
252 handle subrepos safely on qrecord
253
253
254 $ mkrepo repo-2499-qrecord
254 $ mkrepo repo-2499-qrecord
255 $ testadd qrecord --config ui.interactive=1 -m0 0.diff <<EOF
255 $ testadd qrecord --config ui.interactive=1 -m0 0.diff <<EOF
256 > y
256 > y
257 > y
257 > y
258 > EOF
258 > EOF
259 adding a
259 adding a
260 % abort when adding .hgsub w/dirty subrepo
260 % abort when adding .hgsub w/dirty subrepo
261 A .hgsub
261 A .hgsub
262 A sub/a
262 A sub/a
263 % qrecord --config ui.interactive=1 -m0 0.diff
263 % qrecord --config ui.interactive=1 -m0 0.diff
264 diff --git a/.hgsub b/.hgsub
264 diff --git a/.hgsub b/.hgsub
265 new file mode 100644
265 new file mode 100644
266 examine changes to '.hgsub'? [Ynsfdaq?]
266 examine changes to '.hgsub'? [Ynsfdaq?]
267 abort: uncommitted changes in subrepository sub
267 abort: uncommitted changes in subrepository sub
268 [255]
268 [255]
269 % update substate when adding .hgsub w/clean updated subrepo
269 % update substate when adding .hgsub w/clean updated subrepo
270 A .hgsub
270 A .hgsub
271 % qrecord --config ui.interactive=1 -m0 0.diff
271 % qrecord --config ui.interactive=1 -m0 0.diff
272 diff --git a/.hgsub b/.hgsub
272 diff --git a/.hgsub b/.hgsub
273 new file mode 100644
273 new file mode 100644
274 examine changes to '.hgsub'? [Ynsfdaq?]
274 examine changes to '.hgsub'? [Ynsfdaq?]
275 committing subrepository sub
275 committing subrepository sub
276 path sub
276 path sub
277 source sub
277 source sub
278 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
278 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
279
279
280 $ testmod qrecord --config ui.interactive=1 -m1 1.diff <<EOF
280 $ testmod qrecord --config ui.interactive=1 -m1 1.diff <<EOF
281 > y
281 > y
282 > y
282 > y
283 > EOF
283 > EOF
284 adding a
284 adding a
285 % abort when modifying .hgsub w/dirty subrepo
285 % abort when modifying .hgsub w/dirty subrepo
286 M .hgsub
286 M .hgsub
287 A sub2/a
287 A sub2/a
288 % qrecord --config ui.interactive=1 -m1 1.diff
288 % qrecord --config ui.interactive=1 -m1 1.diff
289 diff --git a/.hgsub b/.hgsub
289 diff --git a/.hgsub b/.hgsub
290 1 hunks, 1 lines changed
290 1 hunks, 1 lines changed
291 examine changes to '.hgsub'? [Ynsfdaq?]
291 examine changes to '.hgsub'? [Ynsfdaq?]
292 @@ -1,1 +1,2 @@
292 @@ -1,1 +1,2 @@
293 sub = sub
293 sub = sub
294 +sub2 = sub2
294 +sub2 = sub2
295 record this change to '.hgsub'? [Ynsfdaq?]
295 record this change to '.hgsub'? [Ynsfdaq?]
296 abort: uncommitted changes in subrepository sub2
296 abort: uncommitted changes in subrepository sub2
297 [255]
297 [255]
298 % update substate when modifying .hgsub w/clean updated subrepo
298 % update substate when modifying .hgsub w/clean updated subrepo
299 M .hgsub
299 M .hgsub
300 % qrecord --config ui.interactive=1 -m1 1.diff
300 % qrecord --config ui.interactive=1 -m1 1.diff
301 diff --git a/.hgsub b/.hgsub
301 diff --git a/.hgsub b/.hgsub
302 1 hunks, 1 lines changed
302 1 hunks, 1 lines changed
303 examine changes to '.hgsub'? [Ynsfdaq?]
303 examine changes to '.hgsub'? [Ynsfdaq?]
304 @@ -1,1 +1,2 @@
304 @@ -1,1 +1,2 @@
305 sub = sub
305 sub = sub
306 +sub2 = sub2
306 +sub2 = sub2
307 record this change to '.hgsub'? [Ynsfdaq?]
307 record this change to '.hgsub'? [Ynsfdaq?]
308 committing subrepository sub2
308 committing subrepository sub2
309 path sub
309 path sub
310 source sub
310 source sub
311 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
311 revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
312 path sub2
312 path sub2
313 source sub2
313 source sub2
314 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
314 revision 1f94c7611cc6b74f5a17b16121a1170d44776845
315
315
316 $ hg qpop -qa
316 $ hg qpop -qa
317 patch queue now empty
317 patch queue now empty
318 $ EXTRA= testrm1 qrecord --config ui.interactive=1 -m2 2.diff <<EOF
318 $ EXTRA= testrm1 qrecord --config ui.interactive=1 -m2 2.diff <<EOF
319 > y
319 > y
320 > y
320 > y
321 > EOF
321 > EOF
322 adding a
322 adding a
323 % update substate when removing .hgsub w/dirty subrepo
323 % update substate when removing .hgsub w/dirty subrepo
324 M sub3/a
324 M sub3/a
325 R .hgsub
325 R .hgsub
326 % qrecord --config ui.interactive=1 -m2 2.diff
326 % qrecord --config ui.interactive=1 -m2 2.diff
327 diff --git a/.hgsub b/.hgsub
327 diff --git a/.hgsub b/.hgsub
328 deleted file mode 100644
328 deleted file mode 100644
329 examine changes to '.hgsub'? [Ynsfdaq?]
329 examine changes to '.hgsub'? [Ynsfdaq?]
330 % debugsub should be empty
330 % debugsub should be empty
331
331
332 $ hg qpop -qa
332 $ hg qpop -qa
333 patch queue now empty
333 patch queue now empty
334 $ EXTRA= testrm2 qrecord --config ui.interactive=1 -m3 3.diff <<EOF
334 $ EXTRA= testrm2 qrecord --config ui.interactive=1 -m3 3.diff <<EOF
335 > y
335 > y
336 > y
336 > y
337 > EOF
337 > EOF
338 adding a
338 adding a
339 % update substate when removing .hgsub w/clean updated subrepo
339 % update substate when removing .hgsub w/clean updated subrepo
340 R .hgsub
340 R .hgsub
341 % qrecord --config ui.interactive=1 -m3 3.diff
341 % qrecord --config ui.interactive=1 -m3 3.diff
342 diff --git a/.hgsub b/.hgsub
342 diff --git a/.hgsub b/.hgsub
343 deleted file mode 100644
343 deleted file mode 100644
344 examine changes to '.hgsub'? [Ynsfdaq?]
344 examine changes to '.hgsub'? [Ynsfdaq?]
345 % debugsub should be empty
345 % debugsub should be empty
346
346
347 $ cd ..
347 $ cd ..
348
349
350 handle svn subrepos safely
351
352 $ svnadmin create svn-repo-2499
353 $ curpath=`pwd | tr '\\\\' /`
354 $ expr "$svnpath" : "\/" > /dev/null
355 > if [ $? -ne 0 ]; then
356 > curpath="/$curpath"
357 > fi
358 $ svnurl="file://$curpath/svn-repo-2499/project"
359 $ mkdir -p svn-project-2499/trunk
360 $ svn import -m 'init project' svn-project-2499 "$svnurl"
361 Adding svn-project-2499/trunk
362
363 Committed revision 1.
364
365 qnew on repo w/svn subrepo
366 $ mkrepo repo-2499-svn-subrepo
367 $ svn co "$svnurl"/trunk sub
368 Checked out revision 1.
369 $ echo 'sub = [svn]sub' >> .hgsub
370 $ hg add .hgsub
371 $ hg status -S
372 A .hgsub
373 ? sub/.svn/entries
374 $ hg qnew -m0 0.diff
375 committing subrepository sub
376 $ cd sub
377 $ echo a > a
378 $ svn add a
379 A a
380 $ svn st
381 A a
382 $ cd ..
383 $ hg status -S # doesn't show status for svn subrepos (yet)
384 $ hg qnew -m1 1.diff
385 abort: uncommitted changes in subrepository sub
386 [255]
General Comments 0
You need to be logged in to leave comments. Login now