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