##// END OF EJS Templates
py3: use stringutil.forcebytestr() to convert error messages to bytes...
Pulkit Goyal -
r37603:df4fd29c default
parent child Browse files
Show More
@@ -1,395 +1,396 b''
1 1 # subrepoutil.py - sub-repository operations and substate handling
2 2 #
3 3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12 import posixpath
13 13 import re
14 14
15 15 from .i18n import _
16 16 from . import (
17 17 config,
18 18 error,
19 19 filemerge,
20 20 pathutil,
21 21 phases,
22 22 util,
23 23 )
24 24 from .utils import (
25 25 stringutil,
26 26 )
27 27
28 28 nullstate = ('', '', 'empty')
29 29
30 30 def state(ctx, ui):
31 31 """return a state dict, mapping subrepo paths configured in .hgsub
32 32 to tuple: (source from .hgsub, revision from .hgsubstate, kind
33 33 (key in types dict))
34 34 """
35 35 p = config.config()
36 36 repo = ctx.repo()
37 37 def read(f, sections=None, remap=None):
38 38 if f in ctx:
39 39 try:
40 40 data = ctx[f].data()
41 41 except IOError as err:
42 42 if err.errno != errno.ENOENT:
43 43 raise
44 44 # handle missing subrepo spec files as removed
45 45 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
46 46 repo.pathto(f))
47 47 return
48 48 p.parse(f, data, sections, remap, read)
49 49 else:
50 50 raise error.Abort(_("subrepo spec file \'%s\' not found") %
51 51 repo.pathto(f))
52 52 if '.hgsub' in ctx:
53 53 read('.hgsub')
54 54
55 55 for path, src in ui.configitems('subpaths'):
56 56 p.set('subpaths', path, src, ui.configsource('subpaths', path))
57 57
58 58 rev = {}
59 59 if '.hgsubstate' in ctx:
60 60 try:
61 61 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
62 62 l = l.lstrip()
63 63 if not l:
64 64 continue
65 65 try:
66 66 revision, path = l.split(" ", 1)
67 67 except ValueError:
68 68 raise error.Abort(_("invalid subrepository revision "
69 69 "specifier in \'%s\' line %d")
70 70 % (repo.pathto('.hgsubstate'), (i + 1)))
71 71 rev[path] = revision
72 72 except IOError as err:
73 73 if err.errno != errno.ENOENT:
74 74 raise
75 75
76 76 def remap(src):
77 77 for pattern, repl in p.items('subpaths'):
78 78 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
79 79 # does a string decode.
80 80 repl = stringutil.escapestr(repl)
81 81 # However, we still want to allow back references to go
82 82 # through unharmed, so we turn r'\\1' into r'\1'. Again,
83 83 # extra escapes are needed because re.sub string decodes.
84 84 repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl)
85 85 try:
86 86 src = re.sub(pattern, repl, src, 1)
87 87 except re.error as e:
88 88 raise error.Abort(_("bad subrepository pattern in %s: %s")
89 % (p.source('subpaths', pattern), e))
89 % (p.source('subpaths', pattern),
90 stringutil.forcebytestr(e)))
90 91 return src
91 92
92 93 state = {}
93 94 for path, src in p[''].items():
94 95 kind = 'hg'
95 96 if src.startswith('['):
96 97 if ']' not in src:
97 98 raise error.Abort(_('missing ] in subrepository source'))
98 99 kind, src = src.split(']', 1)
99 100 kind = kind[1:]
100 101 src = src.lstrip() # strip any extra whitespace after ']'
101 102
102 103 if not util.url(src).isabs():
103 104 parent = _abssource(repo, abort=False)
104 105 if parent:
105 106 parent = util.url(parent)
106 107 parent.path = posixpath.join(parent.path or '', src)
107 108 parent.path = posixpath.normpath(parent.path)
108 109 joined = str(parent)
109 110 # Remap the full joined path and use it if it changes,
110 111 # else remap the original source.
111 112 remapped = remap(joined)
112 113 if remapped == joined:
113 114 src = remap(src)
114 115 else:
115 116 src = remapped
116 117
117 118 src = remap(src)
118 119 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
119 120
120 121 return state
121 122
122 123 def writestate(repo, state):
123 124 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
124 125 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
125 126 if state[s][1] != nullstate[1]]
126 127 repo.wwrite('.hgsubstate', ''.join(lines), '')
127 128
128 129 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
129 130 """delegated from merge.applyupdates: merging of .hgsubstate file
130 131 in working context, merging context and ancestor context"""
131 132 if mctx == actx: # backwards?
132 133 actx = wctx.p1()
133 134 s1 = wctx.substate
134 135 s2 = mctx.substate
135 136 sa = actx.substate
136 137 sm = {}
137 138
138 139 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
139 140
140 141 def debug(s, msg, r=""):
141 142 if r:
142 143 r = "%s:%s:%s" % r
143 144 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
144 145
145 146 promptssrc = filemerge.partextras(labels)
146 147 for s, l in sorted(s1.iteritems()):
147 148 prompts = None
148 149 a = sa.get(s, nullstate)
149 150 ld = l # local state with possible dirty flag for compares
150 151 if wctx.sub(s).dirty():
151 152 ld = (l[0], l[1] + "+")
152 153 if wctx == actx: # overwrite
153 154 a = ld
154 155
155 156 prompts = promptssrc.copy()
156 157 prompts['s'] = s
157 158 if s in s2:
158 159 r = s2[s]
159 160 if ld == r or r == a: # no change or local is newer
160 161 sm[s] = l
161 162 continue
162 163 elif ld == a: # other side changed
163 164 debug(s, "other changed, get", r)
164 165 wctx.sub(s).get(r, overwrite)
165 166 sm[s] = r
166 167 elif ld[0] != r[0]: # sources differ
167 168 prompts['lo'] = l[0]
168 169 prompts['ro'] = r[0]
169 170 if repo.ui.promptchoice(
170 171 _(' subrepository sources for %(s)s differ\n'
171 172 'use (l)ocal%(l)s source (%(lo)s)'
172 173 ' or (r)emote%(o)s source (%(ro)s)?'
173 174 '$$ &Local $$ &Remote') % prompts, 0):
174 175 debug(s, "prompt changed, get", r)
175 176 wctx.sub(s).get(r, overwrite)
176 177 sm[s] = r
177 178 elif ld[1] == a[1]: # local side is unchanged
178 179 debug(s, "other side changed, get", r)
179 180 wctx.sub(s).get(r, overwrite)
180 181 sm[s] = r
181 182 else:
182 183 debug(s, "both sides changed")
183 184 srepo = wctx.sub(s)
184 185 prompts['sl'] = srepo.shortid(l[1])
185 186 prompts['sr'] = srepo.shortid(r[1])
186 187 option = repo.ui.promptchoice(
187 188 _(' subrepository %(s)s diverged (local revision: %(sl)s, '
188 189 'remote revision: %(sr)s)\n'
189 190 '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?'
190 191 '$$ &Merge $$ &Local $$ &Remote')
191 192 % prompts, 0)
192 193 if option == 0:
193 194 wctx.sub(s).merge(r)
194 195 sm[s] = l
195 196 debug(s, "merge with", r)
196 197 elif option == 1:
197 198 sm[s] = l
198 199 debug(s, "keep local subrepo revision", l)
199 200 else:
200 201 wctx.sub(s).get(r, overwrite)
201 202 sm[s] = r
202 203 debug(s, "get remote subrepo revision", r)
203 204 elif ld == a: # remote removed, local unchanged
204 205 debug(s, "remote removed, remove")
205 206 wctx.sub(s).remove()
206 207 elif a == nullstate: # not present in remote or ancestor
207 208 debug(s, "local added, keep")
208 209 sm[s] = l
209 210 continue
210 211 else:
211 212 if repo.ui.promptchoice(
212 213 _(' local%(l)s changed subrepository %(s)s'
213 214 ' which remote%(o)s removed\n'
214 215 'use (c)hanged version or (d)elete?'
215 216 '$$ &Changed $$ &Delete') % prompts, 0):
216 217 debug(s, "prompt remove")
217 218 wctx.sub(s).remove()
218 219
219 220 for s, r in sorted(s2.items()):
220 221 prompts = None
221 222 if s in s1:
222 223 continue
223 224 elif s not in sa:
224 225 debug(s, "remote added, get", r)
225 226 mctx.sub(s).get(r)
226 227 sm[s] = r
227 228 elif r != sa[s]:
228 229 prompts = promptssrc.copy()
229 230 prompts['s'] = s
230 231 if repo.ui.promptchoice(
231 232 _(' remote%(o)s changed subrepository %(s)s'
232 233 ' which local%(l)s removed\n'
233 234 'use (c)hanged version or (d)elete?'
234 235 '$$ &Changed $$ &Delete') % prompts, 0) == 0:
235 236 debug(s, "prompt recreate", r)
236 237 mctx.sub(s).get(r)
237 238 sm[s] = r
238 239
239 240 # record merged .hgsubstate
240 241 writestate(repo, sm)
241 242 return sm
242 243
243 244 def precommit(ui, wctx, status, match, force=False):
244 245 """Calculate .hgsubstate changes that should be applied before committing
245 246
246 247 Returns (subs, commitsubs, newstate) where
247 248 - subs: changed subrepos (including dirty ones)
248 249 - commitsubs: dirty subrepos which the caller needs to commit recursively
249 250 - newstate: new state dict which the caller must write to .hgsubstate
250 251
251 252 This also updates the given status argument.
252 253 """
253 254 subs = []
254 255 commitsubs = set()
255 256 newstate = wctx.substate.copy()
256 257
257 258 # only manage subrepos and .hgsubstate if .hgsub is present
258 259 if '.hgsub' in wctx:
259 260 # we'll decide whether to track this ourselves, thanks
260 261 for c in status.modified, status.added, status.removed:
261 262 if '.hgsubstate' in c:
262 263 c.remove('.hgsubstate')
263 264
264 265 # compare current state to last committed state
265 266 # build new substate based on last committed state
266 267 oldstate = wctx.p1().substate
267 268 for s in sorted(newstate.keys()):
268 269 if not match(s):
269 270 # ignore working copy, use old state if present
270 271 if s in oldstate:
271 272 newstate[s] = oldstate[s]
272 273 continue
273 274 if not force:
274 275 raise error.Abort(
275 276 _("commit with new subrepo %s excluded") % s)
276 277 dirtyreason = wctx.sub(s).dirtyreason(True)
277 278 if dirtyreason:
278 279 if not ui.configbool('ui', 'commitsubrepos'):
279 280 raise error.Abort(dirtyreason,
280 281 hint=_("use --subrepos for recursive commit"))
281 282 subs.append(s)
282 283 commitsubs.add(s)
283 284 else:
284 285 bs = wctx.sub(s).basestate()
285 286 newstate[s] = (newstate[s][0], bs, newstate[s][2])
286 287 if oldstate.get(s, (None, None, None))[1] != bs:
287 288 subs.append(s)
288 289
289 290 # check for removed subrepos
290 291 for p in wctx.parents():
291 292 r = [s for s in p.substate if s not in newstate]
292 293 subs += [s for s in r if match(s)]
293 294 if subs:
294 295 if (not match('.hgsub') and
295 296 '.hgsub' in (wctx.modified() + wctx.added())):
296 297 raise error.Abort(_("can't commit subrepos without .hgsub"))
297 298 status.modified.insert(0, '.hgsubstate')
298 299
299 300 elif '.hgsub' in status.removed:
300 301 # clean up .hgsubstate when .hgsub is removed
301 302 if ('.hgsubstate' in wctx and
302 303 '.hgsubstate' not in (status.modified + status.added +
303 304 status.removed)):
304 305 status.removed.insert(0, '.hgsubstate')
305 306
306 307 return subs, commitsubs, newstate
307 308
308 309 def reporelpath(repo):
309 310 """return path to this (sub)repo as seen from outermost repo"""
310 311 parent = repo
311 312 while util.safehasattr(parent, '_subparent'):
312 313 parent = parent._subparent
313 314 return repo.root[len(pathutil.normasprefix(parent.root)):]
314 315
315 316 def subrelpath(sub):
316 317 """return path to this subrepo as seen from outermost repo"""
317 318 return sub._relpath
318 319
319 320 def _abssource(repo, push=False, abort=True):
320 321 """return pull/push path of repo - either based on parent repo .hgsub info
321 322 or on the top repo config. Abort or return None if no source found."""
322 323 if util.safehasattr(repo, '_subparent'):
323 324 source = util.url(repo._subsource)
324 325 if source.isabs():
325 326 return bytes(source)
326 327 source.path = posixpath.normpath(source.path)
327 328 parent = _abssource(repo._subparent, push, abort=False)
328 329 if parent:
329 330 parent = util.url(util.pconvert(parent))
330 331 parent.path = posixpath.join(parent.path or '', source.path)
331 332 parent.path = posixpath.normpath(parent.path)
332 333 return bytes(parent)
333 334 else: # recursion reached top repo
334 335 path = None
335 336 if util.safehasattr(repo, '_subtoppath'):
336 337 path = repo._subtoppath
337 338 elif push and repo.ui.config('paths', 'default-push'):
338 339 path = repo.ui.config('paths', 'default-push')
339 340 elif repo.ui.config('paths', 'default'):
340 341 path = repo.ui.config('paths', 'default')
341 342 elif repo.shared():
342 343 # chop off the .hg component to get the default path form. This has
343 344 # already run through vfsmod.vfs(..., realpath=True), so it doesn't
344 345 # have problems with 'C:'
345 346 return os.path.dirname(repo.sharedpath)
346 347 if path:
347 348 # issue5770: 'C:\' and 'C:' are not equivalent paths. The former is
348 349 # as expected: an absolute path to the root of the C: drive. The
349 350 # latter is a relative path, and works like so:
350 351 #
351 352 # C:\>cd C:\some\path
352 353 # C:\>D:
353 354 # D:\>python -c "import os; print os.path.abspath('C:')"
354 355 # C:\some\path
355 356 #
356 357 # D:\>python -c "import os; print os.path.abspath('C:relative')"
357 358 # C:\some\path\relative
358 359 if util.hasdriveletter(path):
359 360 if len(path) == 2 or path[2:3] not in br'\/':
360 361 path = os.path.abspath(path)
361 362 return path
362 363
363 364 if abort:
364 365 raise error.Abort(_("default path for subrepository not found"))
365 366
366 367 def newcommitphase(ui, ctx):
367 368 commitphase = phases.newcommitphase(ui)
368 369 substate = getattr(ctx, "substate", None)
369 370 if not substate:
370 371 return commitphase
371 372 check = ui.config('phases', 'checksubrepos')
372 373 if check not in ('ignore', 'follow', 'abort'):
373 374 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
374 375 % (check))
375 376 if check == 'ignore':
376 377 return commitphase
377 378 maxphase = phases.public
378 379 maxsub = None
379 380 for s in sorted(substate):
380 381 sub = ctx.sub(s)
381 382 subphase = sub.phase(substate[s][1])
382 383 if maxphase < subphase:
383 384 maxphase = subphase
384 385 maxsub = s
385 386 if commitphase < maxphase:
386 387 if check == 'abort':
387 388 raise error.Abort(_("can't commit in %s phase"
388 389 " conflicting %s from subrepository %s") %
389 390 (phases.phasenames[commitphase],
390 391 phases.phasenames[maxphase], maxsub))
391 392 ui.warn(_("warning: changes are committed in"
392 393 " %s phase from subrepository %s\n") %
393 394 (phases.phasenames[maxphase], maxsub))
394 395 return maxphase
395 396 return commitphase
General Comments 0
You need to be logged in to leave comments. Login now