##// END OF EJS Templates
merge with crew-stable
Thomas Arendsen Hein -
r17036:ab4644c3 merge default
parent child Browse files
Show More
@@ -1,1266 +1,1266 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 i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
46 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
47 l = l.lstrip()
47 l = l.lstrip()
48 if not l:
48 if not l:
49 continue
49 continue
50 try:
50 try:
51 revision, path = l.split(" ", 1)
51 revision, path = l.split(" ", 1)
52 except ValueError:
52 except ValueError:
53 raise util.Abort(_("invalid subrepository revision "
53 raise util.Abort(_("invalid subrepository revision "
54 "specifier in .hgsubstate line %d")
54 "specifier in .hgsubstate line %d")
55 % (i + 1))
55 % (i + 1))
56 rev[path] = revision
56 rev[path] = revision
57 except IOError, err:
57 except IOError, err:
58 if err.errno != errno.ENOENT:
58 if err.errno != errno.ENOENT:
59 raise
59 raise
60
60
61 def remap(src):
61 def remap(src):
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 return src
75 return src
76
76
77 state = {}
77 state = {}
78 for path, src in p[''].items():
78 for path, src in p[''].items():
79 kind = 'hg'
79 kind = 'hg'
80 if src.startswith('['):
80 if src.startswith('['):
81 if ']' not in src:
81 if ']' not in src:
82 raise util.Abort(_('missing ] in subrepo source'))
82 raise util.Abort(_('missing ] in subrepo source'))
83 kind, src = src.split(']', 1)
83 kind, src = src.split(']', 1)
84 kind = kind[1:]
84 kind = kind[1:]
85 src = src.lstrip() # strip any extra whitespace after ']'
85 src = src.lstrip() # strip any extra whitespace after ']'
86
86
87 if not util.url(src).isabs():
87 if not util.url(src).isabs():
88 parent = _abssource(ctx._repo, abort=False)
88 parent = _abssource(ctx._repo, abort=False)
89 if parent:
89 if parent:
90 parent = util.url(parent)
90 parent = util.url(parent)
91 parent.path = posixpath.join(parent.path or '', src)
91 parent.path = posixpath.join(parent.path or '', src)
92 parent.path = posixpath.normpath(parent.path)
92 parent.path = posixpath.normpath(parent.path)
93 joined = str(parent)
93 joined = str(parent)
94 # Remap the full joined path and use it if it changes,
94 # Remap the full joined path and use it if it changes,
95 # else remap the original source.
95 # else remap the original source.
96 remapped = remap(joined)
96 remapped = remap(joined)
97 if remapped == joined:
97 if remapped == joined:
98 src = remap(src)
98 src = remap(src)
99 else:
99 else:
100 src = remapped
100 src = remapped
101
101
102 src = remap(src)
102 src = remap(src)
103 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
103 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
104
104
105 return state
105 return state
106
106
107 def writestate(repo, state):
107 def writestate(repo, state):
108 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
108 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
109 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
109 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)]
110 repo.wwrite('.hgsubstate', ''.join(lines), '')
110 repo.wwrite('.hgsubstate', ''.join(lines), '')
111
111
112 def submerge(repo, wctx, mctx, actx, overwrite):
112 def submerge(repo, wctx, mctx, actx, overwrite):
113 """delegated from merge.applyupdates: merging of .hgsubstate file
113 """delegated from merge.applyupdates: merging of .hgsubstate file
114 in working context, merging context and ancestor context"""
114 in working context, merging context and ancestor context"""
115 if mctx == actx: # backwards?
115 if mctx == actx: # backwards?
116 actx = wctx.p1()
116 actx = wctx.p1()
117 s1 = wctx.substate
117 s1 = wctx.substate
118 s2 = mctx.substate
118 s2 = mctx.substate
119 sa = actx.substate
119 sa = actx.substate
120 sm = {}
120 sm = {}
121
121
122 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
122 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
123
123
124 def debug(s, msg, r=""):
124 def debug(s, msg, r=""):
125 if r:
125 if r:
126 r = "%s:%s:%s" % r
126 r = "%s:%s:%s" % r
127 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
127 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
128
128
129 for s, l in s1.items():
129 for s, l in s1.items():
130 a = sa.get(s, nullstate)
130 a = sa.get(s, nullstate)
131 ld = l # local state with possible dirty flag for compares
131 ld = l # local state with possible dirty flag for compares
132 if wctx.sub(s).dirty():
132 if wctx.sub(s).dirty():
133 ld = (l[0], l[1] + "+")
133 ld = (l[0], l[1] + "+")
134 if wctx == actx: # overwrite
134 if wctx == actx: # overwrite
135 a = ld
135 a = ld
136
136
137 if s in s2:
137 if s in s2:
138 r = s2[s]
138 r = s2[s]
139 if ld == r or r == a: # no change or local is newer
139 if ld == r or r == a: # no change or local is newer
140 sm[s] = l
140 sm[s] = l
141 continue
141 continue
142 elif ld == a: # other side changed
142 elif ld == a: # other side changed
143 debug(s, "other changed, get", r)
143 debug(s, "other changed, get", r)
144 wctx.sub(s).get(r, overwrite)
144 wctx.sub(s).get(r, overwrite)
145 sm[s] = r
145 sm[s] = r
146 elif ld[0] != r[0]: # sources differ
146 elif ld[0] != r[0]: # sources differ
147 if repo.ui.promptchoice(
147 if repo.ui.promptchoice(
148 _(' subrepository sources for %s differ\n'
148 _(' subrepository sources for %s differ\n'
149 'use (l)ocal source (%s) or (r)emote source (%s)?')
149 'use (l)ocal source (%s) or (r)emote source (%s)?')
150 % (s, l[0], r[0]),
150 % (s, l[0], r[0]),
151 (_('&Local'), _('&Remote')), 0):
151 (_('&Local'), _('&Remote')), 0):
152 debug(s, "prompt changed, get", r)
152 debug(s, "prompt changed, get", r)
153 wctx.sub(s).get(r, overwrite)
153 wctx.sub(s).get(r, overwrite)
154 sm[s] = r
154 sm[s] = r
155 elif ld[1] == a[1]: # local side is unchanged
155 elif ld[1] == a[1]: # local side is unchanged
156 debug(s, "other side changed, get", r)
156 debug(s, "other side changed, get", r)
157 wctx.sub(s).get(r, overwrite)
157 wctx.sub(s).get(r, overwrite)
158 sm[s] = r
158 sm[s] = r
159 else:
159 else:
160 debug(s, "both sides changed, merge with", r)
160 debug(s, "both sides changed, merge with", r)
161 wctx.sub(s).merge(r)
161 wctx.sub(s).merge(r)
162 sm[s] = l
162 sm[s] = l
163 elif ld == a: # remote removed, local unchanged
163 elif ld == a: # remote removed, local unchanged
164 debug(s, "remote removed, remove")
164 debug(s, "remote removed, remove")
165 wctx.sub(s).remove()
165 wctx.sub(s).remove()
166 elif a == nullstate: # not present in remote or ancestor
166 elif a == nullstate: # not present in remote or ancestor
167 debug(s, "local added, keep")
167 debug(s, "local added, keep")
168 sm[s] = l
168 sm[s] = l
169 continue
169 continue
170 else:
170 else:
171 if repo.ui.promptchoice(
171 if repo.ui.promptchoice(
172 _(' local changed subrepository %s which remote removed\n'
172 _(' local changed subrepository %s which remote removed\n'
173 'use (c)hanged version or (d)elete?') % s,
173 'use (c)hanged version or (d)elete?') % s,
174 (_('&Changed'), _('&Delete')), 0):
174 (_('&Changed'), _('&Delete')), 0):
175 debug(s, "prompt remove")
175 debug(s, "prompt remove")
176 wctx.sub(s).remove()
176 wctx.sub(s).remove()
177
177
178 for s, r in sorted(s2.items()):
178 for s, r in sorted(s2.items()):
179 if s in s1:
179 if s in s1:
180 continue
180 continue
181 elif s not in sa:
181 elif s not in sa:
182 debug(s, "remote added, get", r)
182 debug(s, "remote added, get", r)
183 mctx.sub(s).get(r)
183 mctx.sub(s).get(r)
184 sm[s] = r
184 sm[s] = r
185 elif r != sa[s]:
185 elif r != sa[s]:
186 if repo.ui.promptchoice(
186 if repo.ui.promptchoice(
187 _(' remote changed subrepository %s which local removed\n'
187 _(' remote changed subrepository %s which local removed\n'
188 'use (c)hanged version or (d)elete?') % s,
188 'use (c)hanged version or (d)elete?') % s,
189 (_('&Changed'), _('&Delete')), 0) == 0:
189 (_('&Changed'), _('&Delete')), 0) == 0:
190 debug(s, "prompt recreate", r)
190 debug(s, "prompt recreate", r)
191 wctx.sub(s).get(r)
191 wctx.sub(s).get(r)
192 sm[s] = r
192 sm[s] = r
193
193
194 # record merged .hgsubstate
194 # record merged .hgsubstate
195 writestate(repo, sm)
195 writestate(repo, sm)
196
196
197 def _updateprompt(ui, sub, dirty, local, remote):
197 def _updateprompt(ui, sub, dirty, local, remote):
198 if dirty:
198 if dirty:
199 msg = (_(' subrepository sources for %s differ\n'
199 msg = (_(' subrepository sources for %s differ\n'
200 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
200 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
201 % (subrelpath(sub), local, remote))
201 % (subrelpath(sub), local, remote))
202 else:
202 else:
203 msg = (_(' subrepository sources for %s differ (in checked out '
203 msg = (_(' subrepository sources for %s differ (in checked out '
204 'version)\n'
204 'version)\n'
205 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
205 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
206 % (subrelpath(sub), local, remote))
206 % (subrelpath(sub), local, remote))
207 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
207 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
208
208
209 def reporelpath(repo):
209 def reporelpath(repo):
210 """return path to this (sub)repo as seen from outermost repo"""
210 """return path to this (sub)repo as seen from outermost repo"""
211 parent = repo
211 parent = repo
212 while util.safehasattr(parent, '_subparent'):
212 while util.safehasattr(parent, '_subparent'):
213 parent = parent._subparent
213 parent = parent._subparent
214 p = parent.root.rstrip(os.sep)
214 p = parent.root.rstrip(os.sep)
215 return repo.root[len(p) + 1:]
215 return repo.root[len(p) + 1:]
216
216
217 def subrelpath(sub):
217 def subrelpath(sub):
218 """return path to this subrepo as seen from outermost repo"""
218 """return path to this subrepo as seen from outermost repo"""
219 if util.safehasattr(sub, '_relpath'):
219 if util.safehasattr(sub, '_relpath'):
220 return sub._relpath
220 return sub._relpath
221 if not util.safehasattr(sub, '_repo'):
221 if not util.safehasattr(sub, '_repo'):
222 return sub._path
222 return sub._path
223 return reporelpath(sub._repo)
223 return reporelpath(sub._repo)
224
224
225 def _abssource(repo, push=False, abort=True):
225 def _abssource(repo, push=False, abort=True):
226 """return pull/push path of repo - either based on parent repo .hgsub info
226 """return pull/push path of repo - either based on parent repo .hgsub info
227 or on the top repo config. Abort or return None if no source found."""
227 or on the top repo config. Abort or return None if no source found."""
228 if util.safehasattr(repo, '_subparent'):
228 if util.safehasattr(repo, '_subparent'):
229 source = util.url(repo._subsource)
229 source = util.url(repo._subsource)
230 if source.isabs():
230 if source.isabs():
231 return str(source)
231 return str(source)
232 source.path = posixpath.normpath(source.path)
232 source.path = posixpath.normpath(source.path)
233 parent = _abssource(repo._subparent, push, abort=False)
233 parent = _abssource(repo._subparent, push, abort=False)
234 if parent:
234 if parent:
235 parent = util.url(util.pconvert(parent))
235 parent = util.url(util.pconvert(parent))
236 parent.path = posixpath.join(parent.path or '', source.path)
236 parent.path = posixpath.join(parent.path or '', source.path)
237 parent.path = posixpath.normpath(parent.path)
237 parent.path = posixpath.normpath(parent.path)
238 return str(parent)
238 return str(parent)
239 else: # recursion reached top repo
239 else: # recursion reached top repo
240 if util.safehasattr(repo, '_subtoppath'):
240 if util.safehasattr(repo, '_subtoppath'):
241 return repo._subtoppath
241 return repo._subtoppath
242 if push and repo.ui.config('paths', 'default-push'):
242 if push and repo.ui.config('paths', 'default-push'):
243 return repo.ui.config('paths', 'default-push')
243 return repo.ui.config('paths', 'default-push')
244 if repo.ui.config('paths', 'default'):
244 if repo.ui.config('paths', 'default'):
245 return repo.ui.config('paths', 'default')
245 return repo.ui.config('paths', 'default')
246 if abort:
246 if abort:
247 raise util.Abort(_("default path for subrepository %s not found") %
247 raise util.Abort(_("default path for subrepository %s not found") %
248 reporelpath(repo))
248 reporelpath(repo))
249
249
250 def itersubrepos(ctx1, ctx2):
250 def itersubrepos(ctx1, ctx2):
251 """find subrepos in ctx1 or ctx2"""
251 """find subrepos in ctx1 or ctx2"""
252 # Create a (subpath, ctx) mapping where we prefer subpaths from
252 # Create a (subpath, ctx) mapping where we prefer subpaths from
253 # ctx1. The subpaths from ctx2 are important when the .hgsub file
253 # ctx1. The subpaths from ctx2 are important when the .hgsub file
254 # has been modified (in ctx2) but not yet committed (in ctx1).
254 # has been modified (in ctx2) but not yet committed (in ctx1).
255 subpaths = dict.fromkeys(ctx2.substate, ctx2)
255 subpaths = dict.fromkeys(ctx2.substate, ctx2)
256 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
256 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
257 for subpath, ctx in sorted(subpaths.iteritems()):
257 for subpath, ctx in sorted(subpaths.iteritems()):
258 yield subpath, ctx.sub(subpath)
258 yield subpath, ctx.sub(subpath)
259
259
260 def subrepo(ctx, path):
260 def subrepo(ctx, path):
261 """return instance of the right subrepo class for subrepo in path"""
261 """return instance of the right subrepo class for subrepo in path"""
262 # subrepo inherently violates our import layering rules
262 # subrepo inherently violates our import layering rules
263 # because it wants to make repo objects from deep inside the stack
263 # because it wants to make repo objects from deep inside the stack
264 # so we manually delay the circular imports to not break
264 # so we manually delay the circular imports to not break
265 # scripts that don't use our demand-loading
265 # scripts that don't use our demand-loading
266 global hg
266 global hg
267 import hg as h
267 import hg as h
268 hg = h
268 hg = h
269
269
270 scmutil.pathauditor(ctx._repo.root)(path)
270 scmutil.pathauditor(ctx._repo.root)(path)
271 state = ctx.substate[path]
271 state = ctx.substate[path]
272 if state[2] not in types:
272 if state[2] not in types:
273 raise util.Abort(_('unknown subrepo type %s') % state[2])
273 raise util.Abort(_('unknown subrepo type %s') % state[2])
274 return types[state[2]](ctx, path, state[:2])
274 return types[state[2]](ctx, path, state[:2])
275
275
276 # subrepo classes need to implement the following abstract class:
276 # subrepo classes need to implement the following abstract class:
277
277
278 class abstractsubrepo(object):
278 class abstractsubrepo(object):
279
279
280 def dirty(self, ignoreupdate=False):
280 def dirty(self, ignoreupdate=False):
281 """returns true if the dirstate of the subrepo is dirty or does not
281 """returns true if the dirstate of the subrepo is dirty or does not
282 match current stored state. If ignoreupdate is true, only check
282 match current stored state. If ignoreupdate is true, only check
283 whether the subrepo has uncommitted changes in its dirstate.
283 whether the subrepo has uncommitted changes in its dirstate.
284 """
284 """
285 raise NotImplementedError
285 raise NotImplementedError
286
286
287 def basestate(self):
287 def basestate(self):
288 """current working directory base state, disregarding .hgsubstate
288 """current working directory base state, disregarding .hgsubstate
289 state and working directory modifications"""
289 state and working directory modifications"""
290 raise NotImplementedError
290 raise NotImplementedError
291
291
292 def checknested(self, path):
292 def checknested(self, path):
293 """check if path is a subrepository within this repository"""
293 """check if path is a subrepository within this repository"""
294 return False
294 return False
295
295
296 def commit(self, text, user, date):
296 def commit(self, text, user, date):
297 """commit the current changes to the subrepo with the given
297 """commit the current changes to the subrepo with the given
298 log message. Use given user and date if possible. Return the
298 log message. Use given user and date if possible. Return the
299 new state of the subrepo.
299 new state of the subrepo.
300 """
300 """
301 raise NotImplementedError
301 raise NotImplementedError
302
302
303 def remove(self):
303 def remove(self):
304 """remove the subrepo
304 """remove the subrepo
305
305
306 (should verify the dirstate is not dirty first)
306 (should verify the dirstate is not dirty first)
307 """
307 """
308 raise NotImplementedError
308 raise NotImplementedError
309
309
310 def get(self, state, overwrite=False):
310 def get(self, state, overwrite=False):
311 """run whatever commands are needed to put the subrepo into
311 """run whatever commands are needed to put the subrepo into
312 this state
312 this state
313 """
313 """
314 raise NotImplementedError
314 raise NotImplementedError
315
315
316 def merge(self, state):
316 def merge(self, state):
317 """merge currently-saved state with the new state."""
317 """merge currently-saved state with the new state."""
318 raise NotImplementedError
318 raise NotImplementedError
319
319
320 def push(self, opts):
320 def push(self, opts):
321 """perform whatever action is analogous to 'hg push'
321 """perform whatever action is analogous to 'hg push'
322
322
323 This may be a no-op on some systems.
323 This may be a no-op on some systems.
324 """
324 """
325 raise NotImplementedError
325 raise NotImplementedError
326
326
327 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
327 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
328 return []
328 return []
329
329
330 def status(self, rev2, **opts):
330 def status(self, rev2, **opts):
331 return [], [], [], [], [], [], []
331 return [], [], [], [], [], [], []
332
332
333 def diff(self, diffopts, node2, match, prefix, **opts):
333 def diff(self, diffopts, node2, match, prefix, **opts):
334 pass
334 pass
335
335
336 def outgoing(self, ui, dest, opts):
336 def outgoing(self, ui, dest, opts):
337 return 1
337 return 1
338
338
339 def incoming(self, ui, source, opts):
339 def incoming(self, ui, source, opts):
340 return 1
340 return 1
341
341
342 def files(self):
342 def files(self):
343 """return filename iterator"""
343 """return filename iterator"""
344 raise NotImplementedError
344 raise NotImplementedError
345
345
346 def filedata(self, name):
346 def filedata(self, name):
347 """return file data"""
347 """return file data"""
348 raise NotImplementedError
348 raise NotImplementedError
349
349
350 def fileflags(self, name):
350 def fileflags(self, name):
351 """return file flags"""
351 """return file flags"""
352 return ''
352 return ''
353
353
354 def archive(self, ui, archiver, prefix):
354 def archive(self, ui, archiver, prefix):
355 files = self.files()
355 files = self.files()
356 total = len(files)
356 total = len(files)
357 relpath = subrelpath(self)
357 relpath = subrelpath(self)
358 ui.progress(_('archiving (%s)') % relpath, 0,
358 ui.progress(_('archiving (%s)') % relpath, 0,
359 unit=_('files'), total=total)
359 unit=_('files'), total=total)
360 for i, name in enumerate(files):
360 for i, name in enumerate(files):
361 flags = self.fileflags(name)
361 flags = self.fileflags(name)
362 mode = 'x' in flags and 0755 or 0644
362 mode = 'x' in flags and 0755 or 0644
363 symlink = 'l' in flags
363 symlink = 'l' in flags
364 archiver.addfile(os.path.join(prefix, self._path, name),
364 archiver.addfile(os.path.join(prefix, self._path, name),
365 mode, symlink, self.filedata(name))
365 mode, symlink, self.filedata(name))
366 ui.progress(_('archiving (%s)') % relpath, i + 1,
366 ui.progress(_('archiving (%s)') % relpath, i + 1,
367 unit=_('files'), total=total)
367 unit=_('files'), total=total)
368 ui.progress(_('archiving (%s)') % relpath, None)
368 ui.progress(_('archiving (%s)') % relpath, None)
369
369
370 def walk(self, match):
370 def walk(self, match):
371 '''
371 '''
372 walk recursively through the directory tree, finding all files
372 walk recursively through the directory tree, finding all files
373 matched by the match function
373 matched by the match function
374 '''
374 '''
375 pass
375 pass
376
376
377 def forget(self, ui, match, prefix):
377 def forget(self, ui, match, prefix):
378 return ([], [])
378 return ([], [])
379
379
380 def revert(self, ui, substate, *pats, **opts):
380 def revert(self, ui, substate, *pats, **opts):
381 ui.warn('%s: reverting %s subrepos is unsupported\n' \
381 ui.warn('%s: reverting %s subrepos is unsupported\n' \
382 % (substate[0], substate[2]))
382 % (substate[0], substate[2]))
383 return []
383 return []
384
384
385 class hgsubrepo(abstractsubrepo):
385 class hgsubrepo(abstractsubrepo):
386 def __init__(self, ctx, path, state):
386 def __init__(self, ctx, path, state):
387 self._path = path
387 self._path = path
388 self._state = state
388 self._state = state
389 r = ctx._repo
389 r = ctx._repo
390 root = r.wjoin(path)
390 root = r.wjoin(path)
391 create = False
391 create = False
392 if not os.path.exists(os.path.join(root, '.hg')):
392 if not os.path.exists(os.path.join(root, '.hg')):
393 create = True
393 create = True
394 util.makedirs(root)
394 util.makedirs(root)
395 self._repo = hg.repository(r.ui, root, create=create)
395 self._repo = hg.repository(r.ui, root, create=create)
396 self._initrepo(r, state[0], create)
396 self._initrepo(r, state[0], create)
397
397
398 def _initrepo(self, parentrepo, source, create):
398 def _initrepo(self, parentrepo, source, create):
399 self._repo._subparent = parentrepo
399 self._repo._subparent = parentrepo
400 self._repo._subsource = source
400 self._repo._subsource = source
401
401
402 if create:
402 if create:
403 fp = self._repo.opener("hgrc", "w", text=True)
403 fp = self._repo.opener("hgrc", "w", text=True)
404 fp.write('[paths]\n')
404 fp.write('[paths]\n')
405
405
406 def addpathconfig(key, value):
406 def addpathconfig(key, value):
407 if value:
407 if value:
408 fp.write('%s = %s\n' % (key, value))
408 fp.write('%s = %s\n' % (key, value))
409 self._repo.ui.setconfig('paths', key, value)
409 self._repo.ui.setconfig('paths', key, value)
410
410
411 defpath = _abssource(self._repo, abort=False)
411 defpath = _abssource(self._repo, abort=False)
412 defpushpath = _abssource(self._repo, True, abort=False)
412 defpushpath = _abssource(self._repo, True, abort=False)
413 addpathconfig('default', defpath)
413 addpathconfig('default', defpath)
414 if defpath != defpushpath:
414 if defpath != defpushpath:
415 addpathconfig('default-push', defpushpath)
415 addpathconfig('default-push', defpushpath)
416 fp.close()
416 fp.close()
417
417
418 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
418 def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
419 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
419 return cmdutil.add(ui, self._repo, match, dryrun, listsubrepos,
420 os.path.join(prefix, self._path), explicitonly)
420 os.path.join(prefix, self._path), explicitonly)
421
421
422 def status(self, rev2, **opts):
422 def status(self, rev2, **opts):
423 try:
423 try:
424 rev1 = self._state[1]
424 rev1 = self._state[1]
425 ctx1 = self._repo[rev1]
425 ctx1 = self._repo[rev1]
426 ctx2 = self._repo[rev2]
426 ctx2 = self._repo[rev2]
427 return self._repo.status(ctx1, ctx2, **opts)
427 return self._repo.status(ctx1, ctx2, **opts)
428 except error.RepoLookupError, inst:
428 except error.RepoLookupError, inst:
429 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
429 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
430 % (inst, subrelpath(self)))
430 % (inst, subrelpath(self)))
431 return [], [], [], [], [], [], []
431 return [], [], [], [], [], [], []
432
432
433 def diff(self, diffopts, node2, match, prefix, **opts):
433 def diff(self, diffopts, node2, match, prefix, **opts):
434 try:
434 try:
435 node1 = node.bin(self._state[1])
435 node1 = node.bin(self._state[1])
436 # We currently expect node2 to come from substate and be
436 # We currently expect node2 to come from substate and be
437 # in hex format
437 # in hex format
438 if node2 is not None:
438 if node2 is not None:
439 node2 = node.bin(node2)
439 node2 = node.bin(node2)
440 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
440 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
441 node1, node2, match,
441 node1, node2, match,
442 prefix=os.path.join(prefix, self._path),
442 prefix=os.path.join(prefix, self._path),
443 listsubrepos=True, **opts)
443 listsubrepos=True, **opts)
444 except error.RepoLookupError, inst:
444 except error.RepoLookupError, inst:
445 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
445 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
446 % (inst, subrelpath(self)))
446 % (inst, subrelpath(self)))
447
447
448 def archive(self, ui, archiver, prefix):
448 def archive(self, ui, archiver, prefix):
449 self._get(self._state + ('hg',))
449 self._get(self._state + ('hg',))
450 abstractsubrepo.archive(self, ui, archiver, prefix)
450 abstractsubrepo.archive(self, ui, archiver, prefix)
451
451
452 rev = self._state[1]
452 rev = self._state[1]
453 ctx = self._repo[rev]
453 ctx = self._repo[rev]
454 for subpath in ctx.substate:
454 for subpath in ctx.substate:
455 s = subrepo(ctx, subpath)
455 s = subrepo(ctx, subpath)
456 s.archive(ui, archiver, os.path.join(prefix, self._path))
456 s.archive(ui, archiver, os.path.join(prefix, self._path))
457
457
458 def dirty(self, ignoreupdate=False):
458 def dirty(self, ignoreupdate=False):
459 r = self._state[1]
459 r = self._state[1]
460 if r == '' and not ignoreupdate: # no state recorded
460 if r == '' and not ignoreupdate: # no state recorded
461 return True
461 return True
462 w = self._repo[None]
462 w = self._repo[None]
463 if r != w.p1().hex() and not ignoreupdate:
463 if r != w.p1().hex() and not ignoreupdate:
464 # different version checked out
464 # different version checked out
465 return True
465 return True
466 return w.dirty() # working directory changed
466 return w.dirty() # working directory changed
467
467
468 def basestate(self):
468 def basestate(self):
469 return self._repo['.'].hex()
469 return self._repo['.'].hex()
470
470
471 def checknested(self, path):
471 def checknested(self, path):
472 return self._repo._checknested(self._repo.wjoin(path))
472 return self._repo._checknested(self._repo.wjoin(path))
473
473
474 def commit(self, text, user, date):
474 def commit(self, text, user, date):
475 # don't bother committing in the subrepo if it's only been
475 # don't bother committing in the subrepo if it's only been
476 # updated
476 # updated
477 if not self.dirty(True):
477 if not self.dirty(True):
478 return self._repo['.'].hex()
478 return self._repo['.'].hex()
479 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
479 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
480 n = self._repo.commit(text, user, date)
480 n = self._repo.commit(text, user, date)
481 if not n:
481 if not n:
482 return self._repo['.'].hex() # different version checked out
482 return self._repo['.'].hex() # different version checked out
483 return node.hex(n)
483 return node.hex(n)
484
484
485 def remove(self):
485 def remove(self):
486 # we can't fully delete the repository as it may contain
486 # we can't fully delete the repository as it may contain
487 # local-only history
487 # local-only history
488 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
488 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
489 hg.clean(self._repo, node.nullid, False)
489 hg.clean(self._repo, node.nullid, False)
490
490
491 def _get(self, state):
491 def _get(self, state):
492 source, revision, kind = state
492 source, revision, kind = state
493 if revision not in self._repo:
493 if revision not in self._repo:
494 self._repo._subsource = source
494 self._repo._subsource = source
495 srcurl = _abssource(self._repo)
495 srcurl = _abssource(self._repo)
496 other = hg.peer(self._repo.ui, {}, srcurl)
496 other = hg.peer(self._repo.ui, {}, srcurl)
497 if len(self._repo) == 0:
497 if len(self._repo) == 0:
498 self._repo.ui.status(_('cloning subrepo %s from %s\n')
498 self._repo.ui.status(_('cloning subrepo %s from %s\n')
499 % (subrelpath(self), srcurl))
499 % (subrelpath(self), srcurl))
500 parentrepo = self._repo._subparent
500 parentrepo = self._repo._subparent
501 shutil.rmtree(self._repo.path)
501 shutil.rmtree(self._repo.path)
502 other, self._repo = hg.clone(self._repo._subparent.ui, {},
502 other, self._repo = hg.clone(self._repo._subparent.ui, {},
503 other, self._repo.root,
503 other, self._repo.root,
504 update=False)
504 update=False)
505 self._initrepo(parentrepo, source, create=True)
505 self._initrepo(parentrepo, source, create=True)
506 else:
506 else:
507 self._repo.ui.status(_('pulling subrepo %s from %s\n')
507 self._repo.ui.status(_('pulling subrepo %s from %s\n')
508 % (subrelpath(self), srcurl))
508 % (subrelpath(self), srcurl))
509 self._repo.pull(other)
509 self._repo.pull(other)
510 bookmarks.updatefromremote(self._repo.ui, self._repo, other,
510 bookmarks.updatefromremote(self._repo.ui, self._repo, other,
511 srcurl)
511 srcurl)
512
512
513 def get(self, state, overwrite=False):
513 def get(self, state, overwrite=False):
514 self._get(state)
514 self._get(state)
515 source, revision, kind = state
515 source, revision, kind = state
516 self._repo.ui.debug("getting subrepo %s\n" % self._path)
516 self._repo.ui.debug("getting subrepo %s\n" % self._path)
517 hg.clean(self._repo, revision, False)
517 hg.clean(self._repo, revision, False)
518
518
519 def merge(self, state):
519 def merge(self, state):
520 self._get(state)
520 self._get(state)
521 cur = self._repo['.']
521 cur = self._repo['.']
522 dst = self._repo[state[1]]
522 dst = self._repo[state[1]]
523 anc = dst.ancestor(cur)
523 anc = dst.ancestor(cur)
524
524
525 def mergefunc():
525 def mergefunc():
526 if anc == cur and dst.branch() == cur.branch():
526 if anc == cur and dst.branch() == cur.branch():
527 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
527 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
528 hg.update(self._repo, state[1])
528 hg.update(self._repo, state[1])
529 elif anc == dst:
529 elif anc == dst:
530 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
530 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
531 else:
531 else:
532 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
532 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
533 hg.merge(self._repo, state[1], remind=False)
533 hg.merge(self._repo, state[1], remind=False)
534
534
535 wctx = self._repo[None]
535 wctx = self._repo[None]
536 if self.dirty():
536 if self.dirty():
537 if anc != dst:
537 if anc != dst:
538 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
538 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
539 mergefunc()
539 mergefunc()
540 else:
540 else:
541 mergefunc()
541 mergefunc()
542 else:
542 else:
543 mergefunc()
543 mergefunc()
544
544
545 def push(self, opts):
545 def push(self, opts):
546 force = opts.get('force')
546 force = opts.get('force')
547 newbranch = opts.get('new_branch')
547 newbranch = opts.get('new_branch')
548 ssh = opts.get('ssh')
548 ssh = opts.get('ssh')
549
549
550 # push subrepos depth-first for coherent ordering
550 # push subrepos depth-first for coherent ordering
551 c = self._repo['']
551 c = self._repo['']
552 subs = c.substate # only repos that are committed
552 subs = c.substate # only repos that are committed
553 for s in sorted(subs):
553 for s in sorted(subs):
554 if c.sub(s).push(opts) == 0:
554 if c.sub(s).push(opts) == 0:
555 return False
555 return False
556
556
557 dsturl = _abssource(self._repo, True)
557 dsturl = _abssource(self._repo, True)
558 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
558 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
559 (subrelpath(self), dsturl))
559 (subrelpath(self), dsturl))
560 other = hg.peer(self._repo.ui, {'ssh': ssh}, dsturl)
560 other = hg.peer(self._repo.ui, {'ssh': ssh}, dsturl)
561 return self._repo.push(other, force, newbranch=newbranch)
561 return self._repo.push(other, force, newbranch=newbranch)
562
562
563 def outgoing(self, ui, dest, opts):
563 def outgoing(self, ui, dest, opts):
564 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
564 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
565
565
566 def incoming(self, ui, source, opts):
566 def incoming(self, ui, source, opts):
567 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
567 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
568
568
569 def files(self):
569 def files(self):
570 rev = self._state[1]
570 rev = self._state[1]
571 ctx = self._repo[rev]
571 ctx = self._repo[rev]
572 return ctx.manifest()
572 return ctx.manifest()
573
573
574 def filedata(self, name):
574 def filedata(self, name):
575 rev = self._state[1]
575 rev = self._state[1]
576 return self._repo[rev][name].data()
576 return self._repo[rev][name].data()
577
577
578 def fileflags(self, name):
578 def fileflags(self, name):
579 rev = self._state[1]
579 rev = self._state[1]
580 ctx = self._repo[rev]
580 ctx = self._repo[rev]
581 return ctx.flags(name)
581 return ctx.flags(name)
582
582
583 def walk(self, match):
583 def walk(self, match):
584 ctx = self._repo[None]
584 ctx = self._repo[None]
585 return ctx.walk(match)
585 return ctx.walk(match)
586
586
587 def forget(self, ui, match, prefix):
587 def forget(self, ui, match, prefix):
588 return cmdutil.forget(ui, self._repo, match,
588 return cmdutil.forget(ui, self._repo, match,
589 os.path.join(prefix, self._path), True)
589 os.path.join(prefix, self._path), True)
590
590
591 def revert(self, ui, substate, *pats, **opts):
591 def revert(self, ui, substate, *pats, **opts):
592 # reverting a subrepo is a 2 step process:
592 # reverting a subrepo is a 2 step process:
593 # 1. if the no_backup is not set, revert all modified
593 # 1. if the no_backup is not set, revert all modified
594 # files inside the subrepo
594 # files inside the subrepo
595 # 2. update the subrepo to the revision specified in
595 # 2. update the subrepo to the revision specified in
596 # the corresponding substate dictionary
596 # the corresponding substate dictionary
597 ui.status(_('reverting subrepo %s\n') % substate[0])
597 ui.status(_('reverting subrepo %s\n') % substate[0])
598 if not opts.get('no_backup'):
598 if not opts.get('no_backup'):
599 # Revert all files on the subrepo, creating backups
599 # Revert all files on the subrepo, creating backups
600 # Note that this will not recursively revert subrepos
600 # Note that this will not recursively revert subrepos
601 # We could do it if there was a set:subrepos() predicate
601 # We could do it if there was a set:subrepos() predicate
602 opts = opts.copy()
602 opts = opts.copy()
603 opts['date'] = None
603 opts['date'] = None
604 opts['rev'] = substate[1]
604 opts['rev'] = substate[1]
605
605
606 pats = []
606 pats = []
607 if not opts['all']:
607 if not opts['all']:
608 pats = ['set:modified()']
608 pats = ['set:modified()']
609 self.filerevert(ui, *pats, **opts)
609 self.filerevert(ui, *pats, **opts)
610
610
611 # Update the repo to the revision specified in the given substate
611 # Update the repo to the revision specified in the given substate
612 self.get(substate, overwrite=True)
612 self.get(substate, overwrite=True)
613
613
614 def filerevert(self, ui, *pats, **opts):
614 def filerevert(self, ui, *pats, **opts):
615 ctx = self._repo[opts['rev']]
615 ctx = self._repo[opts['rev']]
616 parents = self._repo.dirstate.parents()
616 parents = self._repo.dirstate.parents()
617 if opts['all']:
617 if opts['all']:
618 pats = ['set:modified()']
618 pats = ['set:modified()']
619 else:
619 else:
620 pats = []
620 pats = []
621 cmdutil.revert(ui, self._repo, ctx, parents, *pats, **opts)
621 cmdutil.revert(ui, self._repo, ctx, parents, *pats, **opts)
622
622
623 class svnsubrepo(abstractsubrepo):
623 class svnsubrepo(abstractsubrepo):
624 def __init__(self, ctx, path, state):
624 def __init__(self, ctx, path, state):
625 self._path = path
625 self._path = path
626 self._state = state
626 self._state = state
627 self._ctx = ctx
627 self._ctx = ctx
628 self._ui = ctx._repo.ui
628 self._ui = ctx._repo.ui
629 self._exe = util.findexe('svn')
629 self._exe = util.findexe('svn')
630 if not self._exe:
630 if not self._exe:
631 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
631 raise util.Abort(_("'svn' executable not found for subrepo '%s'")
632 % self._path)
632 % self._path)
633
633
634 def _svncommand(self, commands, filename='', failok=False):
634 def _svncommand(self, commands, filename='', failok=False):
635 cmd = [self._exe]
635 cmd = [self._exe]
636 extrakw = {}
636 extrakw = {}
637 if not self._ui.interactive():
637 if not self._ui.interactive():
638 # Making stdin be a pipe should prevent svn from behaving
638 # Making stdin be a pipe should prevent svn from behaving
639 # interactively even if we can't pass --non-interactive.
639 # interactively even if we can't pass --non-interactive.
640 extrakw['stdin'] = subprocess.PIPE
640 extrakw['stdin'] = subprocess.PIPE
641 # Starting in svn 1.5 --non-interactive is a global flag
641 # Starting in svn 1.5 --non-interactive is a global flag
642 # instead of being per-command, but we need to support 1.4 so
642 # instead of being per-command, but we need to support 1.4 so
643 # we have to be intelligent about what commands take
643 # we have to be intelligent about what commands take
644 # --non-interactive.
644 # --non-interactive.
645 if commands[0] in ('update', 'checkout', 'commit'):
645 if commands[0] in ('update', 'checkout', 'commit'):
646 cmd.append('--non-interactive')
646 cmd.append('--non-interactive')
647 cmd.extend(commands)
647 cmd.extend(commands)
648 if filename is not None:
648 if filename is not None:
649 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
649 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
650 cmd.append(path)
650 cmd.append(path)
651 env = dict(os.environ)
651 env = dict(os.environ)
652 # Avoid localized output, preserve current locale for everything else.
652 # Avoid localized output, preserve current locale for everything else.
653 env['LC_MESSAGES'] = 'C'
653 env['LC_MESSAGES'] = 'C'
654 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
654 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
655 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
655 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
656 universal_newlines=True, env=env, **extrakw)
656 universal_newlines=True, env=env, **extrakw)
657 stdout, stderr = p.communicate()
657 stdout, stderr = p.communicate()
658 stderr = stderr.strip()
658 stderr = stderr.strip()
659 if not failok:
659 if not failok:
660 if p.returncode:
660 if p.returncode:
661 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
661 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
662 if stderr:
662 if stderr:
663 self._ui.warn(stderr + '\n')
663 self._ui.warn(stderr + '\n')
664 return stdout, stderr
664 return stdout, stderr
665
665
666 @propertycache
666 @propertycache
667 def _svnversion(self):
667 def _svnversion(self):
668 output, err = self._svncommand(['--version'], filename=None)
668 output, err = self._svncommand(['--version'], filename=None)
669 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
669 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
670 if not m:
670 if not m:
671 raise util.Abort(_('cannot retrieve svn tool version'))
671 raise util.Abort(_('cannot retrieve svn tool version'))
672 return (int(m.group(1)), int(m.group(2)))
672 return (int(m.group(1)), int(m.group(2)))
673
673
674 def _wcrevs(self):
674 def _wcrevs(self):
675 # Get the working directory revision as well as the last
675 # Get the working directory revision as well as the last
676 # commit revision so we can compare the subrepo state with
676 # commit revision so we can compare the subrepo state with
677 # both. We used to store the working directory one.
677 # both. We used to store the working directory one.
678 output, err = self._svncommand(['info', '--xml'])
678 output, err = self._svncommand(['info', '--xml'])
679 doc = xml.dom.minidom.parseString(output)
679 doc = xml.dom.minidom.parseString(output)
680 entries = doc.getElementsByTagName('entry')
680 entries = doc.getElementsByTagName('entry')
681 lastrev, rev = '0', '0'
681 lastrev, rev = '0', '0'
682 if entries:
682 if entries:
683 rev = str(entries[0].getAttribute('revision')) or '0'
683 rev = str(entries[0].getAttribute('revision')) or '0'
684 commits = entries[0].getElementsByTagName('commit')
684 commits = entries[0].getElementsByTagName('commit')
685 if commits:
685 if commits:
686 lastrev = str(commits[0].getAttribute('revision')) or '0'
686 lastrev = str(commits[0].getAttribute('revision')) or '0'
687 return (lastrev, rev)
687 return (lastrev, rev)
688
688
689 def _wcrev(self):
689 def _wcrev(self):
690 return self._wcrevs()[0]
690 return self._wcrevs()[0]
691
691
692 def _wcchanged(self):
692 def _wcchanged(self):
693 """Return (changes, extchanges, missing) where changes is True
693 """Return (changes, extchanges, missing) where changes is True
694 if the working directory was changed, extchanges is
694 if the working directory was changed, extchanges is
695 True if any of these changes concern an external entry and missing
695 True if any of these changes concern an external entry and missing
696 is True if any change is a missing entry.
696 is True if any change is a missing entry.
697 """
697 """
698 output, err = self._svncommand(['status', '--xml'])
698 output, err = self._svncommand(['status', '--xml'])
699 externals, changes, missing = [], [], []
699 externals, changes, missing = [], [], []
700 doc = xml.dom.minidom.parseString(output)
700 doc = xml.dom.minidom.parseString(output)
701 for e in doc.getElementsByTagName('entry'):
701 for e in doc.getElementsByTagName('entry'):
702 s = e.getElementsByTagName('wc-status')
702 s = e.getElementsByTagName('wc-status')
703 if not s:
703 if not s:
704 continue
704 continue
705 item = s[0].getAttribute('item')
705 item = s[0].getAttribute('item')
706 props = s[0].getAttribute('props')
706 props = s[0].getAttribute('props')
707 path = e.getAttribute('path')
707 path = e.getAttribute('path')
708 if item == 'external':
708 if item == 'external':
709 externals.append(path)
709 externals.append(path)
710 elif item == 'missing':
710 elif item == 'missing':
711 missing.append(path)
711 missing.append(path)
712 if (item not in ('', 'normal', 'unversioned', 'external')
712 if (item not in ('', 'normal', 'unversioned', 'external')
713 or props not in ('', 'none', 'normal')):
713 or props not in ('', 'none', 'normal')):
714 changes.append(path)
714 changes.append(path)
715 for path in changes:
715 for path in changes:
716 for ext in externals:
716 for ext in externals:
717 if path == ext or path.startswith(ext + os.sep):
717 if path == ext or path.startswith(ext + os.sep):
718 return True, True, bool(missing)
718 return True, True, bool(missing)
719 return bool(changes), False, bool(missing)
719 return bool(changes), False, bool(missing)
720
720
721 def dirty(self, ignoreupdate=False):
721 def dirty(self, ignoreupdate=False):
722 if not self._wcchanged()[0]:
722 if not self._wcchanged()[0]:
723 if self._state[1] in self._wcrevs() or ignoreupdate:
723 if self._state[1] in self._wcrevs() or ignoreupdate:
724 return False
724 return False
725 return True
725 return True
726
726
727 def basestate(self):
727 def basestate(self):
728 lastrev, rev = self._wcrevs()
728 lastrev, rev = self._wcrevs()
729 if lastrev != rev:
729 if lastrev != rev:
730 # Last committed rev is not the same than rev. We would
730 # Last committed rev is not the same than rev. We would
731 # like to take lastrev but we do not know if the subrepo
731 # like to take lastrev but we do not know if the subrepo
732 # URL exists at lastrev. Test it and fallback to rev it
732 # URL exists at lastrev. Test it and fallback to rev it
733 # is not there.
733 # is not there.
734 try:
734 try:
735 self._svncommand(['info', '%s@%s' % (self._state[0], lastrev)])
735 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
736 return lastrev
736 return lastrev
737 except error.Abort:
737 except error.Abort:
738 pass
738 pass
739 return rev
739 return rev
740
740
741 def commit(self, text, user, date):
741 def commit(self, text, user, date):
742 # user and date are out of our hands since svn is centralized
742 # user and date are out of our hands since svn is centralized
743 changed, extchanged, missing = self._wcchanged()
743 changed, extchanged, missing = self._wcchanged()
744 if not changed:
744 if not changed:
745 return self.basestate()
745 return self.basestate()
746 if extchanged:
746 if extchanged:
747 # Do not try to commit externals
747 # Do not try to commit externals
748 raise util.Abort(_('cannot commit svn externals'))
748 raise util.Abort(_('cannot commit svn externals'))
749 if missing:
749 if missing:
750 # svn can commit with missing entries but aborting like hg
750 # svn can commit with missing entries but aborting like hg
751 # seems a better approach.
751 # seems a better approach.
752 raise util.Abort(_('cannot commit missing svn entries'))
752 raise util.Abort(_('cannot commit missing svn entries'))
753 commitinfo, err = self._svncommand(['commit', '-m', text])
753 commitinfo, err = self._svncommand(['commit', '-m', text])
754 self._ui.status(commitinfo)
754 self._ui.status(commitinfo)
755 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
755 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
756 if not newrev:
756 if not newrev:
757 if not commitinfo.strip():
757 if not commitinfo.strip():
758 # Sometimes, our definition of "changed" differs from
758 # Sometimes, our definition of "changed" differs from
759 # svn one. For instance, svn ignores missing files
759 # svn one. For instance, svn ignores missing files
760 # when committing. If there are only missing files, no
760 # when committing. If there are only missing files, no
761 # commit is made, no output and no error code.
761 # commit is made, no output and no error code.
762 raise util.Abort(_('failed to commit svn changes'))
762 raise util.Abort(_('failed to commit svn changes'))
763 raise util.Abort(commitinfo.splitlines()[-1])
763 raise util.Abort(commitinfo.splitlines()[-1])
764 newrev = newrev.groups()[0]
764 newrev = newrev.groups()[0]
765 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
765 self._ui.status(self._svncommand(['update', '-r', newrev])[0])
766 return newrev
766 return newrev
767
767
768 def remove(self):
768 def remove(self):
769 if self.dirty():
769 if self.dirty():
770 self._ui.warn(_('not removing repo %s because '
770 self._ui.warn(_('not removing repo %s because '
771 'it has changes.\n' % self._path))
771 'it has changes.\n' % self._path))
772 return
772 return
773 self._ui.note(_('removing subrepo %s\n') % self._path)
773 self._ui.note(_('removing subrepo %s\n') % self._path)
774
774
775 def onerror(function, path, excinfo):
775 def onerror(function, path, excinfo):
776 if function is not os.remove:
776 if function is not os.remove:
777 raise
777 raise
778 # read-only files cannot be unlinked under Windows
778 # read-only files cannot be unlinked under Windows
779 s = os.stat(path)
779 s = os.stat(path)
780 if (s.st_mode & stat.S_IWRITE) != 0:
780 if (s.st_mode & stat.S_IWRITE) != 0:
781 raise
781 raise
782 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
782 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
783 os.remove(path)
783 os.remove(path)
784
784
785 path = self._ctx._repo.wjoin(self._path)
785 path = self._ctx._repo.wjoin(self._path)
786 shutil.rmtree(path, onerror=onerror)
786 shutil.rmtree(path, onerror=onerror)
787 try:
787 try:
788 os.removedirs(os.path.dirname(path))
788 os.removedirs(os.path.dirname(path))
789 except OSError:
789 except OSError:
790 pass
790 pass
791
791
792 def get(self, state, overwrite=False):
792 def get(self, state, overwrite=False):
793 if overwrite:
793 if overwrite:
794 self._svncommand(['revert', '--recursive'])
794 self._svncommand(['revert', '--recursive'])
795 args = ['checkout']
795 args = ['checkout']
796 if self._svnversion >= (1, 5):
796 if self._svnversion >= (1, 5):
797 args.append('--force')
797 args.append('--force')
798 # The revision must be specified at the end of the URL to properly
798 # The revision must be specified at the end of the URL to properly
799 # update to a directory which has since been deleted and recreated.
799 # update to a directory which has since been deleted and recreated.
800 args.append('%s@%s' % (state[0], state[1]))
800 args.append('%s@%s' % (state[0], state[1]))
801 status, err = self._svncommand(args, failok=True)
801 status, err = self._svncommand(args, failok=True)
802 if not re.search('Checked out revision [0-9]+.', status):
802 if not re.search('Checked out revision [0-9]+.', status):
803 if ('is already a working copy for a different URL' in err
803 if ('is already a working copy for a different URL' in err
804 and (self._wcchanged()[:2] == (False, False))):
804 and (self._wcchanged()[:2] == (False, False))):
805 # obstructed but clean working copy, so just blow it away.
805 # obstructed but clean working copy, so just blow it away.
806 self.remove()
806 self.remove()
807 self.get(state, overwrite=False)
807 self.get(state, overwrite=False)
808 return
808 return
809 raise util.Abort((status or err).splitlines()[-1])
809 raise util.Abort((status or err).splitlines()[-1])
810 self._ui.status(status)
810 self._ui.status(status)
811
811
812 def merge(self, state):
812 def merge(self, state):
813 old = self._state[1]
813 old = self._state[1]
814 new = state[1]
814 new = state[1]
815 wcrev = self._wcrev()
815 wcrev = self._wcrev()
816 if new != wcrev:
816 if new != wcrev:
817 dirty = old == wcrev or self._wcchanged()[0]
817 dirty = old == wcrev or self._wcchanged()[0]
818 if _updateprompt(self._ui, self, dirty, wcrev, new):
818 if _updateprompt(self._ui, self, dirty, wcrev, new):
819 self.get(state, False)
819 self.get(state, False)
820
820
821 def push(self, opts):
821 def push(self, opts):
822 # push is a no-op for SVN
822 # push is a no-op for SVN
823 return True
823 return True
824
824
825 def files(self):
825 def files(self):
826 output = self._svncommand(['list', '--recursive', '--xml'])[0]
826 output = self._svncommand(['list', '--recursive', '--xml'])[0]
827 doc = xml.dom.minidom.parseString(output)
827 doc = xml.dom.minidom.parseString(output)
828 paths = []
828 paths = []
829 for e in doc.getElementsByTagName('entry'):
829 for e in doc.getElementsByTagName('entry'):
830 kind = str(e.getAttribute('kind'))
830 kind = str(e.getAttribute('kind'))
831 if kind != 'file':
831 if kind != 'file':
832 continue
832 continue
833 name = ''.join(c.data for c
833 name = ''.join(c.data for c
834 in e.getElementsByTagName('name')[0].childNodes
834 in e.getElementsByTagName('name')[0].childNodes
835 if c.nodeType == c.TEXT_NODE)
835 if c.nodeType == c.TEXT_NODE)
836 paths.append(name)
836 paths.append(name)
837 return paths
837 return paths
838
838
839 def filedata(self, name):
839 def filedata(self, name):
840 return self._svncommand(['cat'], name)[0]
840 return self._svncommand(['cat'], name)[0]
841
841
842
842
843 class gitsubrepo(abstractsubrepo):
843 class gitsubrepo(abstractsubrepo):
844 def __init__(self, ctx, path, state):
844 def __init__(self, ctx, path, state):
845 self._state = state
845 self._state = state
846 self._ctx = ctx
846 self._ctx = ctx
847 self._path = path
847 self._path = path
848 self._relpath = os.path.join(reporelpath(ctx._repo), path)
848 self._relpath = os.path.join(reporelpath(ctx._repo), path)
849 self._abspath = ctx._repo.wjoin(path)
849 self._abspath = ctx._repo.wjoin(path)
850 self._subparent = ctx._repo
850 self._subparent = ctx._repo
851 self._ui = ctx._repo.ui
851 self._ui = ctx._repo.ui
852 self._ensuregit()
852 self._ensuregit()
853
853
854 def _ensuregit(self):
854 def _ensuregit(self):
855 try:
855 try:
856 self._gitexecutable = 'git'
856 self._gitexecutable = 'git'
857 out, err = self._gitnodir(['--version'])
857 out, err = self._gitnodir(['--version'])
858 except OSError, e:
858 except OSError, e:
859 if e.errno != 2 or os.name != 'nt':
859 if e.errno != 2 or os.name != 'nt':
860 raise
860 raise
861 self._gitexecutable = 'git.cmd'
861 self._gitexecutable = 'git.cmd'
862 out, err = self._gitnodir(['--version'])
862 out, err = self._gitnodir(['--version'])
863 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
863 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
864 if not m:
864 if not m:
865 self._ui.warn(_('cannot retrieve git version'))
865 self._ui.warn(_('cannot retrieve git version'))
866 return
866 return
867 version = (int(m.group(1)), m.group(2), m.group(3))
867 version = (int(m.group(1)), m.group(2), m.group(3))
868 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
868 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
869 # despite the docstring comment. For now, error on 1.4.0, warn on
869 # despite the docstring comment. For now, error on 1.4.0, warn on
870 # 1.5.0 but attempt to continue.
870 # 1.5.0 but attempt to continue.
871 if version < (1, 5, 0):
871 if version < (1, 5, 0):
872 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
872 raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
873 elif version < (1, 6, 0):
873 elif version < (1, 6, 0):
874 self._ui.warn(_('git subrepo requires at least 1.6.0 or later'))
874 self._ui.warn(_('git subrepo requires at least 1.6.0 or later'))
875
875
876 def _gitcommand(self, commands, env=None, stream=False):
876 def _gitcommand(self, commands, env=None, stream=False):
877 return self._gitdir(commands, env=env, stream=stream)[0]
877 return self._gitdir(commands, env=env, stream=stream)[0]
878
878
879 def _gitdir(self, commands, env=None, stream=False):
879 def _gitdir(self, commands, env=None, stream=False):
880 return self._gitnodir(commands, env=env, stream=stream,
880 return self._gitnodir(commands, env=env, stream=stream,
881 cwd=self._abspath)
881 cwd=self._abspath)
882
882
883 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
883 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
884 """Calls the git command
884 """Calls the git command
885
885
886 The methods tries to call the git command. versions previor to 1.6.0
886 The methods tries to call the git command. versions previor to 1.6.0
887 are not supported and very probably fail.
887 are not supported and very probably fail.
888 """
888 """
889 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
889 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
890 # unless ui.quiet is set, print git's stderr,
890 # unless ui.quiet is set, print git's stderr,
891 # which is mostly progress and useful info
891 # which is mostly progress and useful info
892 errpipe = None
892 errpipe = None
893 if self._ui.quiet:
893 if self._ui.quiet:
894 errpipe = open(os.devnull, 'w')
894 errpipe = open(os.devnull, 'w')
895 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
895 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
896 cwd=cwd, env=env, close_fds=util.closefds,
896 cwd=cwd, env=env, close_fds=util.closefds,
897 stdout=subprocess.PIPE, stderr=errpipe)
897 stdout=subprocess.PIPE, stderr=errpipe)
898 if stream:
898 if stream:
899 return p.stdout, None
899 return p.stdout, None
900
900
901 retdata = p.stdout.read().strip()
901 retdata = p.stdout.read().strip()
902 # wait for the child to exit to avoid race condition.
902 # wait for the child to exit to avoid race condition.
903 p.wait()
903 p.wait()
904
904
905 if p.returncode != 0 and p.returncode != 1:
905 if p.returncode != 0 and p.returncode != 1:
906 # there are certain error codes that are ok
906 # there are certain error codes that are ok
907 command = commands[0]
907 command = commands[0]
908 if command in ('cat-file', 'symbolic-ref'):
908 if command in ('cat-file', 'symbolic-ref'):
909 return retdata, p.returncode
909 return retdata, p.returncode
910 # for all others, abort
910 # for all others, abort
911 raise util.Abort('git %s error %d in %s' %
911 raise util.Abort('git %s error %d in %s' %
912 (command, p.returncode, self._relpath))
912 (command, p.returncode, self._relpath))
913
913
914 return retdata, p.returncode
914 return retdata, p.returncode
915
915
916 def _gitmissing(self):
916 def _gitmissing(self):
917 return not os.path.exists(os.path.join(self._abspath, '.git'))
917 return not os.path.exists(os.path.join(self._abspath, '.git'))
918
918
919 def _gitstate(self):
919 def _gitstate(self):
920 return self._gitcommand(['rev-parse', 'HEAD'])
920 return self._gitcommand(['rev-parse', 'HEAD'])
921
921
922 def _gitcurrentbranch(self):
922 def _gitcurrentbranch(self):
923 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
923 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
924 if err:
924 if err:
925 current = None
925 current = None
926 return current
926 return current
927
927
928 def _gitremote(self, remote):
928 def _gitremote(self, remote):
929 out = self._gitcommand(['remote', 'show', '-n', remote])
929 out = self._gitcommand(['remote', 'show', '-n', remote])
930 line = out.split('\n')[1]
930 line = out.split('\n')[1]
931 i = line.index('URL: ') + len('URL: ')
931 i = line.index('URL: ') + len('URL: ')
932 return line[i:]
932 return line[i:]
933
933
934 def _githavelocally(self, revision):
934 def _githavelocally(self, revision):
935 out, code = self._gitdir(['cat-file', '-e', revision])
935 out, code = self._gitdir(['cat-file', '-e', revision])
936 return code == 0
936 return code == 0
937
937
938 def _gitisancestor(self, r1, r2):
938 def _gitisancestor(self, r1, r2):
939 base = self._gitcommand(['merge-base', r1, r2])
939 base = self._gitcommand(['merge-base', r1, r2])
940 return base == r1
940 return base == r1
941
941
942 def _gitisbare(self):
942 def _gitisbare(self):
943 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
943 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
944
944
945 def _gitupdatestat(self):
945 def _gitupdatestat(self):
946 """This must be run before git diff-index.
946 """This must be run before git diff-index.
947 diff-index only looks at changes to file stat;
947 diff-index only looks at changes to file stat;
948 this command looks at file contents and updates the stat."""
948 this command looks at file contents and updates the stat."""
949 self._gitcommand(['update-index', '-q', '--refresh'])
949 self._gitcommand(['update-index', '-q', '--refresh'])
950
950
951 def _gitbranchmap(self):
951 def _gitbranchmap(self):
952 '''returns 2 things:
952 '''returns 2 things:
953 a map from git branch to revision
953 a map from git branch to revision
954 a map from revision to branches'''
954 a map from revision to branches'''
955 branch2rev = {}
955 branch2rev = {}
956 rev2branch = {}
956 rev2branch = {}
957
957
958 out = self._gitcommand(['for-each-ref', '--format',
958 out = self._gitcommand(['for-each-ref', '--format',
959 '%(objectname) %(refname)'])
959 '%(objectname) %(refname)'])
960 for line in out.split('\n'):
960 for line in out.split('\n'):
961 revision, ref = line.split(' ')
961 revision, ref = line.split(' ')
962 if (not ref.startswith('refs/heads/') and
962 if (not ref.startswith('refs/heads/') and
963 not ref.startswith('refs/remotes/')):
963 not ref.startswith('refs/remotes/')):
964 continue
964 continue
965 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
965 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
966 continue # ignore remote/HEAD redirects
966 continue # ignore remote/HEAD redirects
967 branch2rev[ref] = revision
967 branch2rev[ref] = revision
968 rev2branch.setdefault(revision, []).append(ref)
968 rev2branch.setdefault(revision, []).append(ref)
969 return branch2rev, rev2branch
969 return branch2rev, rev2branch
970
970
971 def _gittracking(self, branches):
971 def _gittracking(self, branches):
972 'return map of remote branch to local tracking branch'
972 'return map of remote branch to local tracking branch'
973 # assumes no more than one local tracking branch for each remote
973 # assumes no more than one local tracking branch for each remote
974 tracking = {}
974 tracking = {}
975 for b in branches:
975 for b in branches:
976 if b.startswith('refs/remotes/'):
976 if b.startswith('refs/remotes/'):
977 continue
977 continue
978 bname = b.split('/', 2)[2]
978 bname = b.split('/', 2)[2]
979 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
979 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
980 if remote:
980 if remote:
981 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
981 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
982 tracking['refs/remotes/%s/%s' %
982 tracking['refs/remotes/%s/%s' %
983 (remote, ref.split('/', 2)[2])] = b
983 (remote, ref.split('/', 2)[2])] = b
984 return tracking
984 return tracking
985
985
986 def _abssource(self, source):
986 def _abssource(self, source):
987 if '://' not in source:
987 if '://' not in source:
988 # recognize the scp syntax as an absolute source
988 # recognize the scp syntax as an absolute source
989 colon = source.find(':')
989 colon = source.find(':')
990 if colon != -1 and '/' not in source[:colon]:
990 if colon != -1 and '/' not in source[:colon]:
991 return source
991 return source
992 self._subsource = source
992 self._subsource = source
993 return _abssource(self)
993 return _abssource(self)
994
994
995 def _fetch(self, source, revision):
995 def _fetch(self, source, revision):
996 if self._gitmissing():
996 if self._gitmissing():
997 source = self._abssource(source)
997 source = self._abssource(source)
998 self._ui.status(_('cloning subrepo %s from %s\n') %
998 self._ui.status(_('cloning subrepo %s from %s\n') %
999 (self._relpath, source))
999 (self._relpath, source))
1000 self._gitnodir(['clone', source, self._abspath])
1000 self._gitnodir(['clone', source, self._abspath])
1001 if self._githavelocally(revision):
1001 if self._githavelocally(revision):
1002 return
1002 return
1003 self._ui.status(_('pulling subrepo %s from %s\n') %
1003 self._ui.status(_('pulling subrepo %s from %s\n') %
1004 (self._relpath, self._gitremote('origin')))
1004 (self._relpath, self._gitremote('origin')))
1005 # try only origin: the originally cloned repo
1005 # try only origin: the originally cloned repo
1006 self._gitcommand(['fetch'])
1006 self._gitcommand(['fetch'])
1007 if not self._githavelocally(revision):
1007 if not self._githavelocally(revision):
1008 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1008 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
1009 (revision, self._relpath))
1009 (revision, self._relpath))
1010
1010
1011 def dirty(self, ignoreupdate=False):
1011 def dirty(self, ignoreupdate=False):
1012 if self._gitmissing():
1012 if self._gitmissing():
1013 return self._state[1] != ''
1013 return self._state[1] != ''
1014 if self._gitisbare():
1014 if self._gitisbare():
1015 return True
1015 return True
1016 if not ignoreupdate and self._state[1] != self._gitstate():
1016 if not ignoreupdate and self._state[1] != self._gitstate():
1017 # different version checked out
1017 # different version checked out
1018 return True
1018 return True
1019 # check for staged changes or modified files; ignore untracked files
1019 # check for staged changes or modified files; ignore untracked files
1020 self._gitupdatestat()
1020 self._gitupdatestat()
1021 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1021 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1022 return code == 1
1022 return code == 1
1023
1023
1024 def basestate(self):
1024 def basestate(self):
1025 return self._gitstate()
1025 return self._gitstate()
1026
1026
1027 def get(self, state, overwrite=False):
1027 def get(self, state, overwrite=False):
1028 source, revision, kind = state
1028 source, revision, kind = state
1029 if not revision:
1029 if not revision:
1030 self.remove()
1030 self.remove()
1031 return
1031 return
1032 self._fetch(source, revision)
1032 self._fetch(source, revision)
1033 # if the repo was set to be bare, unbare it
1033 # if the repo was set to be bare, unbare it
1034 if self._gitisbare():
1034 if self._gitisbare():
1035 self._gitcommand(['config', 'core.bare', 'false'])
1035 self._gitcommand(['config', 'core.bare', 'false'])
1036 if self._gitstate() == revision:
1036 if self._gitstate() == revision:
1037 self._gitcommand(['reset', '--hard', 'HEAD'])
1037 self._gitcommand(['reset', '--hard', 'HEAD'])
1038 return
1038 return
1039 elif self._gitstate() == revision:
1039 elif self._gitstate() == revision:
1040 if overwrite:
1040 if overwrite:
1041 # first reset the index to unmark new files for commit, because
1041 # first reset the index to unmark new files for commit, because
1042 # reset --hard will otherwise throw away files added for commit,
1042 # reset --hard will otherwise throw away files added for commit,
1043 # not just unmark them.
1043 # not just unmark them.
1044 self._gitcommand(['reset', 'HEAD'])
1044 self._gitcommand(['reset', 'HEAD'])
1045 self._gitcommand(['reset', '--hard', 'HEAD'])
1045 self._gitcommand(['reset', '--hard', 'HEAD'])
1046 return
1046 return
1047 branch2rev, rev2branch = self._gitbranchmap()
1047 branch2rev, rev2branch = self._gitbranchmap()
1048
1048
1049 def checkout(args):
1049 def checkout(args):
1050 cmd = ['checkout']
1050 cmd = ['checkout']
1051 if overwrite:
1051 if overwrite:
1052 # first reset the index to unmark new files for commit, because
1052 # first reset the index to unmark new files for commit, because
1053 # the -f option will otherwise throw away files added for
1053 # the -f option will otherwise throw away files added for
1054 # commit, not just unmark them.
1054 # commit, not just unmark them.
1055 self._gitcommand(['reset', 'HEAD'])
1055 self._gitcommand(['reset', 'HEAD'])
1056 cmd.append('-f')
1056 cmd.append('-f')
1057 self._gitcommand(cmd + args)
1057 self._gitcommand(cmd + args)
1058
1058
1059 def rawcheckout():
1059 def rawcheckout():
1060 # no branch to checkout, check it out with no branch
1060 # no branch to checkout, check it out with no branch
1061 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1061 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1062 self._relpath)
1062 self._relpath)
1063 self._ui.warn(_('check out a git branch if you intend '
1063 self._ui.warn(_('check out a git branch if you intend '
1064 'to make changes\n'))
1064 'to make changes\n'))
1065 checkout(['-q', revision])
1065 checkout(['-q', revision])
1066
1066
1067 if revision not in rev2branch:
1067 if revision not in rev2branch:
1068 rawcheckout()
1068 rawcheckout()
1069 return
1069 return
1070 branches = rev2branch[revision]
1070 branches = rev2branch[revision]
1071 firstlocalbranch = None
1071 firstlocalbranch = None
1072 for b in branches:
1072 for b in branches:
1073 if b == 'refs/heads/master':
1073 if b == 'refs/heads/master':
1074 # master trumps all other branches
1074 # master trumps all other branches
1075 checkout(['refs/heads/master'])
1075 checkout(['refs/heads/master'])
1076 return
1076 return
1077 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1077 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1078 firstlocalbranch = b
1078 firstlocalbranch = b
1079 if firstlocalbranch:
1079 if firstlocalbranch:
1080 checkout([firstlocalbranch])
1080 checkout([firstlocalbranch])
1081 return
1081 return
1082
1082
1083 tracking = self._gittracking(branch2rev.keys())
1083 tracking = self._gittracking(branch2rev.keys())
1084 # choose a remote branch already tracked if possible
1084 # choose a remote branch already tracked if possible
1085 remote = branches[0]
1085 remote = branches[0]
1086 if remote not in tracking:
1086 if remote not in tracking:
1087 for b in branches:
1087 for b in branches:
1088 if b in tracking:
1088 if b in tracking:
1089 remote = b
1089 remote = b
1090 break
1090 break
1091
1091
1092 if remote not in tracking:
1092 if remote not in tracking:
1093 # create a new local tracking branch
1093 # create a new local tracking branch
1094 local = remote.split('/', 2)[2]
1094 local = remote.split('/', 2)[2]
1095 checkout(['-b', local, remote])
1095 checkout(['-b', local, remote])
1096 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1096 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1097 # When updating to a tracked remote branch,
1097 # When updating to a tracked remote branch,
1098 # if the local tracking branch is downstream of it,
1098 # if the local tracking branch is downstream of it,
1099 # a normal `git pull` would have performed a "fast-forward merge"
1099 # a normal `git pull` would have performed a "fast-forward merge"
1100 # which is equivalent to updating the local branch to the remote.
1100 # which is equivalent to updating the local branch to the remote.
1101 # Since we are only looking at branching at update, we need to
1101 # Since we are only looking at branching at update, we need to
1102 # detect this situation and perform this action lazily.
1102 # detect this situation and perform this action lazily.
1103 if tracking[remote] != self._gitcurrentbranch():
1103 if tracking[remote] != self._gitcurrentbranch():
1104 checkout([tracking[remote]])
1104 checkout([tracking[remote]])
1105 self._gitcommand(['merge', '--ff', remote])
1105 self._gitcommand(['merge', '--ff', remote])
1106 else:
1106 else:
1107 # a real merge would be required, just checkout the revision
1107 # a real merge would be required, just checkout the revision
1108 rawcheckout()
1108 rawcheckout()
1109
1109
1110 def commit(self, text, user, date):
1110 def commit(self, text, user, date):
1111 if self._gitmissing():
1111 if self._gitmissing():
1112 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1112 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1113 cmd = ['commit', '-a', '-m', text]
1113 cmd = ['commit', '-a', '-m', text]
1114 env = os.environ.copy()
1114 env = os.environ.copy()
1115 if user:
1115 if user:
1116 cmd += ['--author', user]
1116 cmd += ['--author', user]
1117 if date:
1117 if date:
1118 # git's date parser silently ignores when seconds < 1e9
1118 # git's date parser silently ignores when seconds < 1e9
1119 # convert to ISO8601
1119 # convert to ISO8601
1120 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1120 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1121 '%Y-%m-%dT%H:%M:%S %1%2')
1121 '%Y-%m-%dT%H:%M:%S %1%2')
1122 self._gitcommand(cmd, env=env)
1122 self._gitcommand(cmd, env=env)
1123 # make sure commit works otherwise HEAD might not exist under certain
1123 # make sure commit works otherwise HEAD might not exist under certain
1124 # circumstances
1124 # circumstances
1125 return self._gitstate()
1125 return self._gitstate()
1126
1126
1127 def merge(self, state):
1127 def merge(self, state):
1128 source, revision, kind = state
1128 source, revision, kind = state
1129 self._fetch(source, revision)
1129 self._fetch(source, revision)
1130 base = self._gitcommand(['merge-base', revision, self._state[1]])
1130 base = self._gitcommand(['merge-base', revision, self._state[1]])
1131 self._gitupdatestat()
1131 self._gitupdatestat()
1132 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1132 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1133
1133
1134 def mergefunc():
1134 def mergefunc():
1135 if base == revision:
1135 if base == revision:
1136 self.get(state) # fast forward merge
1136 self.get(state) # fast forward merge
1137 elif base != self._state[1]:
1137 elif base != self._state[1]:
1138 self._gitcommand(['merge', '--no-commit', revision])
1138 self._gitcommand(['merge', '--no-commit', revision])
1139
1139
1140 if self.dirty():
1140 if self.dirty():
1141 if self._gitstate() != revision:
1141 if self._gitstate() != revision:
1142 dirty = self._gitstate() == self._state[1] or code != 0
1142 dirty = self._gitstate() == self._state[1] or code != 0
1143 if _updateprompt(self._ui, self, dirty,
1143 if _updateprompt(self._ui, self, dirty,
1144 self._state[1][:7], revision[:7]):
1144 self._state[1][:7], revision[:7]):
1145 mergefunc()
1145 mergefunc()
1146 else:
1146 else:
1147 mergefunc()
1147 mergefunc()
1148
1148
1149 def push(self, opts):
1149 def push(self, opts):
1150 force = opts.get('force')
1150 force = opts.get('force')
1151
1151
1152 if not self._state[1]:
1152 if not self._state[1]:
1153 return True
1153 return True
1154 if self._gitmissing():
1154 if self._gitmissing():
1155 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1155 raise util.Abort(_("subrepo %s is missing") % self._relpath)
1156 # if a branch in origin contains the revision, nothing to do
1156 # if a branch in origin contains the revision, nothing to do
1157 branch2rev, rev2branch = self._gitbranchmap()
1157 branch2rev, rev2branch = self._gitbranchmap()
1158 if self._state[1] in rev2branch:
1158 if self._state[1] in rev2branch:
1159 for b in rev2branch[self._state[1]]:
1159 for b in rev2branch[self._state[1]]:
1160 if b.startswith('refs/remotes/origin/'):
1160 if b.startswith('refs/remotes/origin/'):
1161 return True
1161 return True
1162 for b, revision in branch2rev.iteritems():
1162 for b, revision in branch2rev.iteritems():
1163 if b.startswith('refs/remotes/origin/'):
1163 if b.startswith('refs/remotes/origin/'):
1164 if self._gitisancestor(self._state[1], revision):
1164 if self._gitisancestor(self._state[1], revision):
1165 return True
1165 return True
1166 # otherwise, try to push the currently checked out branch
1166 # otherwise, try to push the currently checked out branch
1167 cmd = ['push']
1167 cmd = ['push']
1168 if force:
1168 if force:
1169 cmd.append('--force')
1169 cmd.append('--force')
1170
1170
1171 current = self._gitcurrentbranch()
1171 current = self._gitcurrentbranch()
1172 if current:
1172 if current:
1173 # determine if the current branch is even useful
1173 # determine if the current branch is even useful
1174 if not self._gitisancestor(self._state[1], current):
1174 if not self._gitisancestor(self._state[1], current):
1175 self._ui.warn(_('unrelated git branch checked out '
1175 self._ui.warn(_('unrelated git branch checked out '
1176 'in subrepo %s\n') % self._relpath)
1176 'in subrepo %s\n') % self._relpath)
1177 return False
1177 return False
1178 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1178 self._ui.status(_('pushing branch %s of subrepo %s\n') %
1179 (current.split('/', 2)[2], self._relpath))
1179 (current.split('/', 2)[2], self._relpath))
1180 self._gitcommand(cmd + ['origin', current])
1180 self._gitcommand(cmd + ['origin', current])
1181 return True
1181 return True
1182 else:
1182 else:
1183 self._ui.warn(_('no branch checked out in subrepo %s\n'
1183 self._ui.warn(_('no branch checked out in subrepo %s\n'
1184 'cannot push revision %s') %
1184 'cannot push revision %s') %
1185 (self._relpath, self._state[1]))
1185 (self._relpath, self._state[1]))
1186 return False
1186 return False
1187
1187
1188 def remove(self):
1188 def remove(self):
1189 if self._gitmissing():
1189 if self._gitmissing():
1190 return
1190 return
1191 if self.dirty():
1191 if self.dirty():
1192 self._ui.warn(_('not removing repo %s because '
1192 self._ui.warn(_('not removing repo %s because '
1193 'it has changes.\n') % self._relpath)
1193 'it has changes.\n') % self._relpath)
1194 return
1194 return
1195 # we can't fully delete the repository as it may contain
1195 # we can't fully delete the repository as it may contain
1196 # local-only history
1196 # local-only history
1197 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1197 self._ui.note(_('removing subrepo %s\n') % self._relpath)
1198 self._gitcommand(['config', 'core.bare', 'true'])
1198 self._gitcommand(['config', 'core.bare', 'true'])
1199 for f in os.listdir(self._abspath):
1199 for f in os.listdir(self._abspath):
1200 if f == '.git':
1200 if f == '.git':
1201 continue
1201 continue
1202 path = os.path.join(self._abspath, f)
1202 path = os.path.join(self._abspath, f)
1203 if os.path.isdir(path) and not os.path.islink(path):
1203 if os.path.isdir(path) and not os.path.islink(path):
1204 shutil.rmtree(path)
1204 shutil.rmtree(path)
1205 else:
1205 else:
1206 os.remove(path)
1206 os.remove(path)
1207
1207
1208 def archive(self, ui, archiver, prefix):
1208 def archive(self, ui, archiver, prefix):
1209 source, revision = self._state
1209 source, revision = self._state
1210 if not revision:
1210 if not revision:
1211 return
1211 return
1212 self._fetch(source, revision)
1212 self._fetch(source, revision)
1213
1213
1214 # Parse git's native archive command.
1214 # Parse git's native archive command.
1215 # This should be much faster than manually traversing the trees
1215 # This should be much faster than manually traversing the trees
1216 # and objects with many subprocess calls.
1216 # and objects with many subprocess calls.
1217 tarstream = self._gitcommand(['archive', revision], stream=True)
1217 tarstream = self._gitcommand(['archive', revision], stream=True)
1218 tar = tarfile.open(fileobj=tarstream, mode='r|')
1218 tar = tarfile.open(fileobj=tarstream, mode='r|')
1219 relpath = subrelpath(self)
1219 relpath = subrelpath(self)
1220 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1220 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1221 for i, info in enumerate(tar):
1221 for i, info in enumerate(tar):
1222 if info.isdir():
1222 if info.isdir():
1223 continue
1223 continue
1224 if info.issym():
1224 if info.issym():
1225 data = info.linkname
1225 data = info.linkname
1226 else:
1226 else:
1227 data = tar.extractfile(info).read()
1227 data = tar.extractfile(info).read()
1228 archiver.addfile(os.path.join(prefix, self._path, info.name),
1228 archiver.addfile(os.path.join(prefix, self._path, info.name),
1229 info.mode, info.issym(), data)
1229 info.mode, info.issym(), data)
1230 ui.progress(_('archiving (%s)') % relpath, i + 1,
1230 ui.progress(_('archiving (%s)') % relpath, i + 1,
1231 unit=_('files'))
1231 unit=_('files'))
1232 ui.progress(_('archiving (%s)') % relpath, None)
1232 ui.progress(_('archiving (%s)') % relpath, None)
1233
1233
1234
1234
1235 def status(self, rev2, **opts):
1235 def status(self, rev2, **opts):
1236 rev1 = self._state[1]
1236 rev1 = self._state[1]
1237 if self._gitmissing() or not rev1:
1237 if self._gitmissing() or not rev1:
1238 # if the repo is missing, return no results
1238 # if the repo is missing, return no results
1239 return [], [], [], [], [], [], []
1239 return [], [], [], [], [], [], []
1240 modified, added, removed = [], [], []
1240 modified, added, removed = [], [], []
1241 self._gitupdatestat()
1241 self._gitupdatestat()
1242 if rev2:
1242 if rev2:
1243 command = ['diff-tree', rev1, rev2]
1243 command = ['diff-tree', rev1, rev2]
1244 else:
1244 else:
1245 command = ['diff-index', rev1]
1245 command = ['diff-index', rev1]
1246 out = self._gitcommand(command)
1246 out = self._gitcommand(command)
1247 for line in out.split('\n'):
1247 for line in out.split('\n'):
1248 tab = line.find('\t')
1248 tab = line.find('\t')
1249 if tab == -1:
1249 if tab == -1:
1250 continue
1250 continue
1251 status, f = line[tab - 1], line[tab + 1:]
1251 status, f = line[tab - 1], line[tab + 1:]
1252 if status == 'M':
1252 if status == 'M':
1253 modified.append(f)
1253 modified.append(f)
1254 elif status == 'A':
1254 elif status == 'A':
1255 added.append(f)
1255 added.append(f)
1256 elif status == 'D':
1256 elif status == 'D':
1257 removed.append(f)
1257 removed.append(f)
1258
1258
1259 deleted = unknown = ignored = clean = []
1259 deleted = unknown = ignored = clean = []
1260 return modified, added, removed, deleted, unknown, ignored, clean
1260 return modified, added, removed, deleted, unknown, ignored, clean
1261
1261
1262 types = {
1262 types = {
1263 'hg': hgsubrepo,
1263 'hg': hgsubrepo,
1264 'svn': svnsubrepo,
1264 'svn': svnsubrepo,
1265 'git': gitsubrepo,
1265 'git': gitsubrepo,
1266 }
1266 }
@@ -1,287 +1,284 b''
1 $ . "$TESTDIR/bzr-definitions"
1 $ . "$TESTDIR/bzr-definitions"
2
2
3 create and rename on the same file in the same step
3 create and rename on the same file in the same step
4
4
5 $ mkdir test-createandrename
5 $ mkdir test-createandrename
6 $ cd test-createandrename
6 $ cd test-createandrename
7 $ bzr init -q source
7 $ bzr init -q source
8
8
9 test empty repo conversion (issue3233)
9 test empty repo conversion (issue3233)
10
10
11 $ hg convert source source-hg
11 $ hg convert source source-hg
12 initializing destination source-hg repository
12 initializing destination source-hg repository
13 scanning source...
13 scanning source...
14 sorting...
14 sorting...
15 converting...
15 converting...
16
16
17 back to the rename stuff
17 back to the rename stuff
18
18
19 $ cd source
19 $ cd source
20 $ echo a > a
20 $ echo a > a
21 $ echo c > c
21 $ echo c > c
22 $ echo e > e
22 $ echo e > e
23 $ bzr add -q a c e
23 $ bzr add -q a c e
24 $ bzr commit -q -m 'Initial add: a, c, e'
24 $ bzr commit -q -m 'Initial add: a, c, e'
25 $ bzr mv a b
25 $ bzr mv a b
26 a => b
26 a => b
27 $ bzr mv c d
27 $ bzr mv c d
28 c => d
28 c => d
29 $ bzr mv e f
29 $ bzr mv e f
30 e => f
30 e => f
31 $ echo a2 >> a
31 $ echo a2 >> a
32 $ mkdir e
32 $ mkdir e
33 $ bzr add -q a e
33 $ bzr add -q a e
34 $ bzr commit -q -m 'rename a into b, create a, rename c into d'
34 $ bzr commit -q -m 'rename a into b, create a, rename c into d'
35 $ cd ..
35 $ cd ..
36 $ hg convert source source-hg
36 $ hg convert source source-hg
37 scanning source...
37 scanning source...
38 sorting...
38 sorting...
39 converting...
39 converting...
40 1 Initial add: a, c, e
40 1 Initial add: a, c, e
41 0 rename a into b, create a, rename c into d
41 0 rename a into b, create a, rename c into d
42 $ glog -R source-hg
42 $ glog -R source-hg
43 o 1@source "rename a into b, create a, rename c into d" files: a b c d e f
43 o 1@source "rename a into b, create a, rename c into d" files: a b c d e f
44 |
44 |
45 o 0@source "Initial add: a, c, e" files: a c e
45 o 0@source "Initial add: a, c, e" files: a c e
46
46
47
47
48 manifest
48 manifest
49
49
50 $ hg manifest -R source-hg -r tip
50 $ hg manifest -R source-hg -r tip
51 a
51 a
52 b
52 b
53 d
53 d
54 f
54 f
55
55
56 test --rev option
56 test --rev option
57
57
58 $ hg convert -r 1 source source-1-hg
58 $ hg convert -r 1 source source-1-hg
59 initializing destination source-1-hg repository
59 initializing destination source-1-hg repository
60 scanning source...
60 scanning source...
61 sorting...
61 sorting...
62 converting...
62 converting...
63 0 Initial add: a, c, e
63 0 Initial add: a, c, e
64 $ glog -R source-1-hg
64 $ glog -R source-1-hg
65 o 0@source "Initial add: a, c, e" files: a c e
65 o 0@source "Initial add: a, c, e" files: a c e
66
66
67
67
68 test with filemap
68 test with filemap
69
69
70 $ cat > filemap <<EOF
70 $ cat > filemap <<EOF
71 > exclude a
71 > exclude a
72 > EOF
72 > EOF
73 $ hg convert --filemap filemap source source-filemap-hg
73 $ hg convert --filemap filemap source source-filemap-hg
74 initializing destination source-filemap-hg repository
74 initializing destination source-filemap-hg repository
75 scanning source...
75 scanning source...
76 sorting...
76 sorting...
77 converting...
77 converting...
78 1 Initial add: a, c, e
78 1 Initial add: a, c, e
79 0 rename a into b, create a, rename c into d
79 0 rename a into b, create a, rename c into d
80 $ hg -R source-filemap-hg manifest -r tip
80 $ hg -R source-filemap-hg manifest -r tip
81 b
81 b
82 d
82 d
83 f
83 f
84
84
85 convert from lightweight checkout
85 convert from lightweight checkout
86
86
87 $ bzr checkout --lightweight source source-light
87 $ bzr checkout --lightweight source source-light
88 $ hg convert -s bzr source-light source-light-hg
88 $ hg convert -s bzr source-light source-light-hg
89 initializing destination source-light-hg repository
89 initializing destination source-light-hg repository
90 warning: lightweight checkouts may cause conversion failures, try with a regular branch instead.
90 warning: lightweight checkouts may cause conversion failures, try with a regular branch instead.
91 $TESTTMP/test-createandrename/source-light does not look like a Bazaar repository
91 $TESTTMP/test-createandrename/source-light does not look like a Bazaar repository
92 abort: source-light: missing or unsupported repository
92 abort: source-light: missing or unsupported repository
93 [255]
93 [255]
94
94
95 extract timestamps that look just like hg's {date|isodate}:
95 extract timestamps that look just like hg's {date|isodate}:
96 yyyy-mm-dd HH:MM zzzz (no seconds!)
96 yyyy-mm-dd HH:MM zzzz (no seconds!)
97 compare timestamps
97 compare timestamps
98
98
99 $ cd source
99 $ cd source
100 $ bzr log | \
100 $ bzr log | \
101 > sed '/timestamp/!d;s/.\{15\}\([0-9: -]\{16\}\):.. \(.[0-9]\{4\}\)/\1 \2/' \
101 > sed '/timestamp/!d;s/.\{15\}\([0-9: -]\{16\}\):.. \(.[0-9]\{4\}\)/\1 \2/' \
102 > > ../bzr-timestamps
102 > > ../bzr-timestamps
103 $ cd ..
103 $ cd ..
104 $ hg -R source-hg log --template "{date|isodate}\n" > hg-timestamps
104 $ hg -R source-hg log --template "{date|isodate}\n" > hg-timestamps
105 $ diff -u bzr-timestamps hg-timestamps
105 $ diff -u bzr-timestamps hg-timestamps
106 $ cd ..
106 $ cd ..
107
107
108 merge
108 merge
109
109
110 $ mkdir test-merge
110 $ mkdir test-merge
111 $ cd test-merge
111 $ cd test-merge
112 $ cat > helper.py <<EOF
112 $ cat > helper.py <<EOF
113 > import sys
113 > import sys
114 > from bzrlib import workingtree
114 > from bzrlib import workingtree
115 > wt = workingtree.WorkingTree.open('.')
115 > wt = workingtree.WorkingTree.open('.')
116 >
116 >
117 > message, stamp = sys.argv[1:]
117 > message, stamp = sys.argv[1:]
118 > wt.commit(message, timestamp=int(stamp))
118 > wt.commit(message, timestamp=int(stamp))
119 > EOF
119 > EOF
120 $ bzr init -q source
120 $ bzr init -q source
121 $ cd source
121 $ cd source
122 $ echo content > a
122 $ echo content > a
123 $ echo content2 > b
123 $ echo content2 > b
124 $ bzr add -q a b
124 $ bzr add -q a b
125 $ bzr commit -q -m 'Initial add'
125 $ bzr commit -q -m 'Initial add'
126 $ cd ..
126 $ cd ..
127 $ bzr branch -q source source-improve
127 $ bzr branch -q source source-improve
128 $ cd source
128 $ cd source
129 $ echo more >> a
129 $ echo more >> a
130 $ python ../helper.py 'Editing a' 100
130 $ python ../helper.py 'Editing a' 100
131 $ cd ../source-improve
131 $ cd ../source-improve
132 $ echo content3 >> b
132 $ echo content3 >> b
133 $ python ../helper.py 'Editing b' 200
133 $ python ../helper.py 'Editing b' 200
134 $ cd ../source
134 $ cd ../source
135 $ bzr merge -q ../source-improve
135 $ bzr merge -q ../source-improve
136 $ bzr commit -q -m 'Merged improve branch'
136 $ bzr commit -q -m 'Merged improve branch'
137 $ cd ..
137 $ cd ..
138 $ hg convert --datesort source source-hg
138 $ hg convert --datesort source source-hg
139 initializing destination source-hg repository
139 initializing destination source-hg repository
140 scanning source...
140 scanning source...
141 sorting...
141 sorting...
142 converting...
142 converting...
143 3 Initial add
143 3 Initial add
144 2 Editing a
144 2 Editing a
145 1 Editing b
145 1 Editing b
146 0 Merged improve branch
146 0 Merged improve branch
147 $ glog -R source-hg
147 $ glog -R source-hg
148 o 3@source "Merged improve branch" files:
148 o 3@source "Merged improve branch" files:
149 |\
149 |\
150 | o 2@source-improve "Editing b" files: b
150 | o 2@source-improve "Editing b" files: b
151 | |
151 | |
152 o | 1@source "Editing a" files: a
152 o | 1@source "Editing a" files: a
153 |/
153 |/
154 o 0@source "Initial add" files: a b
154 o 0@source "Initial add" files: a b
155
155
156 $ cd ..
156 $ cd ..
157
157
158 #if symlink execbit
158 #if symlink execbit
159
159
160 symlinks and executable files
160 symlinks and executable files
161
161
162 $ mkdir test-symlinks
162 $ mkdir test-symlinks
163 $ cd test-symlinks
163 $ cd test-symlinks
164 $ bzr init -q source
164 $ bzr init -q source
165 $ cd source
165 $ cd source
166 $ touch program
166 $ touch program
167 $ chmod +x program
167 $ chmod +x program
168 $ ln -s program altname
168 $ ln -s program altname
169 $ mkdir d
169 $ mkdir d
170 $ echo a > d/a
170 $ echo a > d/a
171 $ ln -s a syma
171 $ ln -s a syma
172 $ bzr add -q altname program syma d/a
172 $ bzr add -q altname program syma d/a
173 $ bzr commit -q -m 'Initial setup'
173 $ bzr commit -q -m 'Initial setup'
174 $ touch newprog
174 $ touch newprog
175 $ chmod +x newprog
175 $ chmod +x newprog
176 $ rm altname
176 $ rm altname
177 $ ln -s newprog altname
177 $ ln -s newprog altname
178 $ chmod -x program
178 $ chmod -x program
179 $ bzr add -q newprog
179 $ bzr add -q newprog
180 $ bzr commit -q -m 'Symlink changed, x bits changed'
180 $ bzr commit -q -m 'Symlink changed, x bits changed'
181 $ cd ..
181 $ cd ..
182 $ hg convert source source-hg
182 $ hg convert source source-hg
183 initializing destination source-hg repository
183 initializing destination source-hg repository
184 scanning source...
184 scanning source...
185 sorting...
185 sorting...
186 converting...
186 converting...
187 1 Initial setup
187 1 Initial setup
188 0 Symlink changed, x bits changed
188 0 Symlink changed, x bits changed
189 $ manifest source-hg 0
189 $ manifest source-hg 0
190 % manifest of 0
190 % manifest of 0
191 644 @ altname
191 644 @ altname
192 644 d/a
192 644 d/a
193 755 * program
193 755 * program
194 644 @ syma
194 644 @ syma
195 $ manifest source-hg tip
195 $ manifest source-hg tip
196 % manifest of tip
196 % manifest of tip
197 644 @ altname
197 644 @ altname
198 644 d/a
198 644 d/a
199 755 * newprog
199 755 * newprog
200 644 program
200 644 program
201 644 @ syma
201 644 @ syma
202
202
203 test the symlinks can be recreated
203 test the symlinks can be recreated
204
204
205 $ cd source-hg
205 $ cd source-hg
206 $ hg up
206 $ hg up
207 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
207 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
208 $ hg cat syma; echo
208 $ hg cat syma; echo
209 a
209 a
210 $ cd ../..
210 $ cd ../..
211
211
212 #endif
212 #endif
213
213
214 Multiple branches
214 Multiple branches
215
215
216 $ bzr init-repo -q --no-trees repo
216 $ bzr init-repo -q --no-trees repo
217 $ bzr init -q repo/trunk
217 $ bzr init -q repo/trunk
218 $ bzr co repo/trunk repo-trunk
218 $ bzr co repo/trunk repo-trunk
219 $ cd repo-trunk
219 $ cd repo-trunk
220 $ echo a > a
220 $ echo a > a
221 $ bzr add a
221 $ bzr add -q a
222 adding a
222 $ bzr ci -qm adda
223 $ bzr ci -qm adda --commit-time '2012-01-01 00:00:01 +0000'
224 $ bzr tag trunk-tag
223 $ bzr tag trunk-tag
225 Created tag trunk-tag.
224 Created tag trunk-tag.
226 $ bzr switch -b branch
225 $ bzr switch -b branch
227 Tree is up to date at revision 1.
226 Tree is up to date at revision 1.
228 Switched to branch: *repo/branch/ (glob)
227 Switched to branch: *repo/branch/ (glob)
229 $ echo b > b
228 $ echo b > b
230 $ bzr add b
229 $ bzr add -q b
231 adding b
230 $ bzr ci -qm addb
232 $ bzr ci -qm addb --commit-time '2012-01-01 00:00:02 +0000'
233 $ bzr tag branch-tag
231 $ bzr tag branch-tag
234 Created tag branch-tag.
232 Created tag branch-tag.
235 $ bzr switch --force ../repo/trunk
233 $ bzr switch --force ../repo/trunk
236 Updated to revision 1.
234 Updated to revision 1.
237 Switched to branch: */repo/trunk/ (glob)
235 Switched to branch: */repo/trunk/ (glob)
238 $ echo a >> a
236 $ echo a >> a
239 $ bzr ci -qm changea --commit-time '2012-01-01 00:00:03 +0000'
237 $ bzr ci -qm changea
240 $ cd ..
238 $ cd ..
241 $ hg convert --datesort repo repo-bzr
239 $ hg convert --datesort repo repo-bzr
242 initializing destination repo-bzr repository
240 initializing destination repo-bzr repository
243 scanning source...
241 scanning source...
244 sorting...
242 sorting...
245 converting...
243 converting...
246 2 adda
244 2 adda
247 1 addb
245 1 addb
248 0 changea
246 0 changea
249 updating tags
247 updating tags
250 $ (cd repo-bzr; glog)
248 $ (cd repo-bzr; glog)
251 o 3@default "update tags" files: .hgtags
249 o 3@default "update tags" files: .hgtags
252 |
250 |
253 o 2@default "changea" files: a
251 o 2@default "changea" files: a
254 |
252 |
255 | o 1@branch "addb" files: b
253 | o 1@branch "addb" files: b
256 |/
254 |/
257 o 0@default "adda" files: a
255 o 0@default "adda" files: a
258
256
259
257
260 Test tags (converted identifiers are not stable because bzr ones are
258 Test tags (converted identifiers are not stable because bzr ones are
261 not and get incorporated in extra fields).
259 not and get incorporated in extra fields).
262
260
263 $ hg -R repo-bzr tags
261 $ hg -R repo-bzr tags
264 tip 3:* (glob)
262 tip 3:* (glob)
265 branch-tag 1:* (glob)
263 branch-tag 1:* (glob)
266 trunk-tag 0:* (glob)
264 trunk-tag 0:* (glob)
267
265
268 Nested repositories (issue3254)
266 Nested repositories (issue3254)
269
267
270 $ bzr init-repo -q --no-trees repo/inner
268 $ bzr init-repo -q --no-trees repo/inner
271 $ bzr init -q repo/inner/trunk
269 $ bzr init -q repo/inner/trunk
272 $ bzr co repo/inner/trunk inner-trunk
270 $ bzr co repo/inner/trunk inner-trunk
273 $ cd inner-trunk
271 $ cd inner-trunk
274 $ echo b > b
272 $ echo b > b
275 $ bzr add b
273 $ bzr add -q b
276 adding b
277 $ bzr ci -qm addb
274 $ bzr ci -qm addb
278 $ cd ..
275 $ cd ..
279 $ hg convert --datesort repo noinner-bzr
276 $ hg convert --datesort repo noinner-bzr
280 initializing destination noinner-bzr repository
277 initializing destination noinner-bzr repository
281 scanning source...
278 scanning source...
282 sorting...
279 sorting...
283 converting...
280 converting...
284 2 adda
281 2 adda
285 1 addb
282 1 addb
286 0 changea
283 0 changea
287 updating tags
284 updating tags
General Comments 0
You need to be logged in to leave comments. Login now