##// END OF EJS Templates
addremove: support addremove with explicit paths in subrepos...
Matt Harbison -
r23539:cb42050f default
parent child Browse files
Show More
@@ -1,158 +1,160 b''
1 1 Subrepositories let you nest external repositories or projects into a
2 2 parent Mercurial repository, and make commands operate on them as a
3 3 group.
4 4
5 5 Mercurial currently supports Mercurial, Git, and Subversion
6 6 subrepositories.
7 7
8 8 Subrepositories are made of three components:
9 9
10 10 1. Nested repository checkouts. They can appear anywhere in the
11 11 parent working directory.
12 12
13 13 2. Nested repository references. They are defined in ``.hgsub``, which
14 14 should be placed in the root of working directory, and
15 15 tell where the subrepository checkouts come from. Mercurial
16 16 subrepositories are referenced like::
17 17
18 18 path/to/nested = https://example.com/nested/repo/path
19 19
20 20 Git and Subversion subrepos are also supported::
21 21
22 22 path/to/nested = [git]git://example.com/nested/repo/path
23 23 path/to/nested = [svn]https://example.com/nested/trunk/path
24 24
25 25 where ``path/to/nested`` is the checkout location relatively to the
26 26 parent Mercurial root, and ``https://example.com/nested/repo/path``
27 27 is the source repository path. The source can also reference a
28 28 filesystem path.
29 29
30 30 Note that ``.hgsub`` does not exist by default in Mercurial
31 31 repositories, you have to create and add it to the parent
32 32 repository before using subrepositories.
33 33
34 34 3. Nested repository states. They are defined in ``.hgsubstate``, which
35 35 is placed in the root of working directory, and
36 36 capture whatever information is required to restore the
37 37 subrepositories to the state they were committed in a parent
38 38 repository changeset. Mercurial automatically record the nested
39 39 repositories states when committing in the parent repository.
40 40
41 41 .. note::
42 42
43 43 The ``.hgsubstate`` file should not be edited manually.
44 44
45 45
46 46 Adding a Subrepository
47 47 ======================
48 48
49 49 If ``.hgsub`` does not exist, create it and add it to the parent
50 50 repository. Clone or checkout the external projects where you want it
51 51 to live in the parent repository. Edit ``.hgsub`` and add the
52 52 subrepository entry as described above. At this point, the
53 53 subrepository is tracked and the next commit will record its state in
54 54 ``.hgsubstate`` and bind it to the committed changeset.
55 55
56 56 Synchronizing a Subrepository
57 57 =============================
58 58
59 59 Subrepos do not automatically track the latest changeset of their
60 60 sources. Instead, they are updated to the changeset that corresponds
61 61 with the changeset checked out in the top-level changeset. This is so
62 62 developers always get a consistent set of compatible code and
63 63 libraries when they update.
64 64
65 65 Thus, updating subrepos is a manual process. Simply check out target
66 66 subrepo at the desired revision, test in the top-level repo, then
67 67 commit in the parent repository to record the new combination.
68 68
69 69 Deleting a Subrepository
70 70 ========================
71 71
72 72 To remove a subrepository from the parent repository, delete its
73 73 reference from ``.hgsub``, then remove its files.
74 74
75 75 Interaction with Mercurial Commands
76 76 ===================================
77 77
78 78 :add: add does not recurse in subrepos unless -S/--subrepos is
79 79 specified. However, if you specify the full path of a file in a
80 80 subrepo, it will be added even without -S/--subrepos specified.
81 81 Git and Subversion subrepositories are currently silently
82 82 ignored.
83 83
84 84 :addremove: addremove does not recurse into subrepos unless
85 -S/--subrepos is specified. Git and Subversion subrepositories
86 will print a warning and continue.
85 -S/--subrepos is specified. However, if you specify the full
86 path of a directory in a subrepo, addremove will be performed on
87 it even without -S/--subrepos being specified. Git and
88 Subversion subrepositories will print a warning and continue.
87 89
88 90 :archive: archive does not recurse in subrepositories unless
89 91 -S/--subrepos is specified.
90 92
91 93 :cat: cat currently only handles exact file matches in subrepos.
92 94 Git and Subversion subrepositories are currently ignored.
93 95
94 96 :commit: commit creates a consistent snapshot of the state of the
95 97 entire project and its subrepositories. If any subrepositories
96 98 have been modified, Mercurial will abort. Mercurial can be made
97 99 to instead commit all modified subrepositories by specifying
98 100 -S/--subrepos, or setting "ui.commitsubrepos=True" in a
99 101 configuration file (see :hg:`help config`). After there are no
100 102 longer any modified subrepositories, it records their state and
101 103 finally commits it in the parent repository. The --addremove
102 104 option also honors the -S/--subrepos option. However, Git and
103 105 Subversion subrepositories will print a warning and abort.
104 106
105 107 :diff: diff does not recurse in subrepos unless -S/--subrepos is
106 108 specified. Changes are displayed as usual, on the subrepositories
107 109 elements. Git subrepositories do not support --include/--exclude.
108 110 Subversion subrepositories are currently silently ignored.
109 111
110 112 :forget: forget currently only handles exact file matches in subrepos.
111 113 Git and Subversion subrepositories are currently silently ignored.
112 114
113 115 :incoming: incoming does not recurse in subrepos unless -S/--subrepos
114 116 is specified. Git and Subversion subrepositories are currently
115 117 silently ignored.
116 118
117 119 :outgoing: outgoing does not recurse in subrepos unless -S/--subrepos
118 120 is specified. Git and Subversion subrepositories are currently
119 121 silently ignored.
120 122
121 123 :pull: pull is not recursive since it is not clear what to pull prior
122 124 to running :hg:`update`. Listing and retrieving all
123 125 subrepositories changes referenced by the parent repository pulled
124 126 changesets is expensive at best, impossible in the Subversion
125 127 case.
126 128
127 129 :push: Mercurial will automatically push all subrepositories first
128 130 when the parent repository is being pushed. This ensures new
129 131 subrepository changes are available when referenced by top-level
130 132 repositories. Push is a no-op for Subversion subrepositories.
131 133
132 134 :status: status does not recurse into subrepositories unless
133 135 -S/--subrepos is specified. Subrepository changes are displayed as
134 136 regular Mercurial changes on the subrepository
135 137 elements. Subversion subrepositories are currently silently
136 138 ignored.
137 139
138 140 :remove: remove does not recurse into subrepositories unless
139 141 -S/--subrepos is specified. However, if you specify a file or
140 142 directory path in a subrepo, it will be removed even without
141 143 -S/--subrepos. Git and Subversion subrepositories are currently
142 144 silently ignored.
143 145
144 146 :update: update restores the subrepos in the state they were
145 147 originally committed in target changeset. If the recorded
146 148 changeset is not available in the current subrepository, Mercurial
147 149 will pull it in first before updating. This means that updating
148 150 can require network access when using subrepositories.
149 151
150 152 Remapping Subrepositories Sources
151 153 =================================
152 154
153 155 A subrepository source location may change during a project life,
154 156 invalidating references stored in the parent repository history. To
155 157 fix this, rewriting rules can be defined in parent repository ``hgrc``
156 158 file or in Mercurial configuration. See the ``[subpaths]`` section in
157 159 hgrc(5) for more details.
158 160
@@ -1,1087 +1,1095 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright 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 i18n import _
9 9 from mercurial.node import nullrev
10 10 import util, error, osutil, revset, similar, encoding, phases, parsers
11 11 import pathutil
12 12 import match as matchmod
13 13 import os, errno, re, glob, tempfile
14 14
15 15 if os.name == 'nt':
16 16 import scmwindows as scmplatform
17 17 else:
18 18 import scmposix as scmplatform
19 19
20 20 systemrcpath = scmplatform.systemrcpath
21 21 userrcpath = scmplatform.userrcpath
22 22
23 23 class status(tuple):
24 24 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
25 25 and 'ignored' properties are only relevant to the working copy.
26 26 '''
27 27
28 28 __slots__ = ()
29 29
30 30 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
31 31 clean):
32 32 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
33 33 ignored, clean))
34 34
35 35 @property
36 36 def modified(self):
37 37 '''files that have been modified'''
38 38 return self[0]
39 39
40 40 @property
41 41 def added(self):
42 42 '''files that have been added'''
43 43 return self[1]
44 44
45 45 @property
46 46 def removed(self):
47 47 '''files that have been removed'''
48 48 return self[2]
49 49
50 50 @property
51 51 def deleted(self):
52 52 '''files that are in the dirstate, but have been deleted from the
53 53 working copy (aka "missing")
54 54 '''
55 55 return self[3]
56 56
57 57 @property
58 58 def unknown(self):
59 59 '''files not in the dirstate that are not ignored'''
60 60 return self[4]
61 61
62 62 @property
63 63 def ignored(self):
64 64 '''files not in the dirstate that are ignored (by _dirignore())'''
65 65 return self[5]
66 66
67 67 @property
68 68 def clean(self):
69 69 '''files that have not been modified'''
70 70 return self[6]
71 71
72 72 def __repr__(self, *args, **kwargs):
73 73 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
74 74 'unknown=%r, ignored=%r, clean=%r>') % self)
75 75
76 76 def itersubrepos(ctx1, ctx2):
77 77 """find subrepos in ctx1 or ctx2"""
78 78 # Create a (subpath, ctx) mapping where we prefer subpaths from
79 79 # ctx1. The subpaths from ctx2 are important when the .hgsub file
80 80 # has been modified (in ctx2) but not yet committed (in ctx1).
81 81 subpaths = dict.fromkeys(ctx2.substate, ctx2)
82 82 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
83 83 for subpath, ctx in sorted(subpaths.iteritems()):
84 84 yield subpath, ctx.sub(subpath)
85 85
86 86 def nochangesfound(ui, repo, excluded=None):
87 87 '''Report no changes for push/pull, excluded is None or a list of
88 88 nodes excluded from the push/pull.
89 89 '''
90 90 secretlist = []
91 91 if excluded:
92 92 for n in excluded:
93 93 if n not in repo:
94 94 # discovery should not have included the filtered revision,
95 95 # we have to explicitly exclude it until discovery is cleanup.
96 96 continue
97 97 ctx = repo[n]
98 98 if ctx.phase() >= phases.secret and not ctx.extinct():
99 99 secretlist.append(n)
100 100
101 101 if secretlist:
102 102 ui.status(_("no changes found (ignored %d secret changesets)\n")
103 103 % len(secretlist))
104 104 else:
105 105 ui.status(_("no changes found\n"))
106 106
107 107 def checknewlabel(repo, lbl, kind):
108 108 # Do not use the "kind" parameter in ui output.
109 109 # It makes strings difficult to translate.
110 110 if lbl in ['tip', '.', 'null']:
111 111 raise util.Abort(_("the name '%s' is reserved") % lbl)
112 112 for c in (':', '\0', '\n', '\r'):
113 113 if c in lbl:
114 114 raise util.Abort(_("%r cannot be used in a name") % c)
115 115 try:
116 116 int(lbl)
117 117 raise util.Abort(_("cannot use an integer as a name"))
118 118 except ValueError:
119 119 pass
120 120
121 121 def checkfilename(f):
122 122 '''Check that the filename f is an acceptable filename for a tracked file'''
123 123 if '\r' in f or '\n' in f:
124 124 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
125 125
126 126 def checkportable(ui, f):
127 127 '''Check if filename f is portable and warn or abort depending on config'''
128 128 checkfilename(f)
129 129 abort, warn = checkportabilityalert(ui)
130 130 if abort or warn:
131 131 msg = util.checkwinfilename(f)
132 132 if msg:
133 133 msg = "%s: %r" % (msg, f)
134 134 if abort:
135 135 raise util.Abort(msg)
136 136 ui.warn(_("warning: %s\n") % msg)
137 137
138 138 def checkportabilityalert(ui):
139 139 '''check if the user's config requests nothing, a warning, or abort for
140 140 non-portable filenames'''
141 141 val = ui.config('ui', 'portablefilenames', 'warn')
142 142 lval = val.lower()
143 143 bval = util.parsebool(val)
144 144 abort = os.name == 'nt' or lval == 'abort'
145 145 warn = bval or lval == 'warn'
146 146 if bval is None and not (warn or abort or lval == 'ignore'):
147 147 raise error.ConfigError(
148 148 _("ui.portablefilenames value is invalid ('%s')") % val)
149 149 return abort, warn
150 150
151 151 class casecollisionauditor(object):
152 152 def __init__(self, ui, abort, dirstate):
153 153 self._ui = ui
154 154 self._abort = abort
155 155 allfiles = '\0'.join(dirstate._map)
156 156 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
157 157 self._dirstate = dirstate
158 158 # The purpose of _newfiles is so that we don't complain about
159 159 # case collisions if someone were to call this object with the
160 160 # same filename twice.
161 161 self._newfiles = set()
162 162
163 163 def __call__(self, f):
164 164 if f in self._newfiles:
165 165 return
166 166 fl = encoding.lower(f)
167 167 if fl in self._loweredfiles and f not in self._dirstate:
168 168 msg = _('possible case-folding collision for %s') % f
169 169 if self._abort:
170 170 raise util.Abort(msg)
171 171 self._ui.warn(_("warning: %s\n") % msg)
172 172 self._loweredfiles.add(fl)
173 173 self._newfiles.add(f)
174 174
175 175 class abstractvfs(object):
176 176 """Abstract base class; cannot be instantiated"""
177 177
178 178 def __init__(self, *args, **kwargs):
179 179 '''Prevent instantiation; don't call this from subclasses.'''
180 180 raise NotImplementedError('attempted instantiating ' + str(type(self)))
181 181
182 182 def tryread(self, path):
183 183 '''gracefully return an empty string for missing files'''
184 184 try:
185 185 return self.read(path)
186 186 except IOError, inst:
187 187 if inst.errno != errno.ENOENT:
188 188 raise
189 189 return ""
190 190
191 191 def tryreadlines(self, path, mode='rb'):
192 192 '''gracefully return an empty array for missing files'''
193 193 try:
194 194 return self.readlines(path, mode=mode)
195 195 except IOError, inst:
196 196 if inst.errno != errno.ENOENT:
197 197 raise
198 198 return []
199 199
200 200 def open(self, path, mode="r", text=False, atomictemp=False,
201 201 notindexed=False):
202 202 '''Open ``path`` file, which is relative to vfs root.
203 203
204 204 Newly created directories are marked as "not to be indexed by
205 205 the content indexing service", if ``notindexed`` is specified
206 206 for "write" mode access.
207 207 '''
208 208 self.open = self.__call__
209 209 return self.__call__(path, mode, text, atomictemp, notindexed)
210 210
211 211 def read(self, path):
212 212 fp = self(path, 'rb')
213 213 try:
214 214 return fp.read()
215 215 finally:
216 216 fp.close()
217 217
218 218 def readlines(self, path, mode='rb'):
219 219 fp = self(path, mode=mode)
220 220 try:
221 221 return fp.readlines()
222 222 finally:
223 223 fp.close()
224 224
225 225 def write(self, path, data):
226 226 fp = self(path, 'wb')
227 227 try:
228 228 return fp.write(data)
229 229 finally:
230 230 fp.close()
231 231
232 232 def writelines(self, path, data, mode='wb', notindexed=False):
233 233 fp = self(path, mode=mode, notindexed=notindexed)
234 234 try:
235 235 return fp.writelines(data)
236 236 finally:
237 237 fp.close()
238 238
239 239 def append(self, path, data):
240 240 fp = self(path, 'ab')
241 241 try:
242 242 return fp.write(data)
243 243 finally:
244 244 fp.close()
245 245
246 246 def chmod(self, path, mode):
247 247 return os.chmod(self.join(path), mode)
248 248
249 249 def exists(self, path=None):
250 250 return os.path.exists(self.join(path))
251 251
252 252 def fstat(self, fp):
253 253 return util.fstat(fp)
254 254
255 255 def isdir(self, path=None):
256 256 return os.path.isdir(self.join(path))
257 257
258 258 def isfile(self, path=None):
259 259 return os.path.isfile(self.join(path))
260 260
261 261 def islink(self, path=None):
262 262 return os.path.islink(self.join(path))
263 263
264 264 def lexists(self, path=None):
265 265 return os.path.lexists(self.join(path))
266 266
267 267 def lstat(self, path=None):
268 268 return os.lstat(self.join(path))
269 269
270 270 def listdir(self, path=None):
271 271 return os.listdir(self.join(path))
272 272
273 273 def makedir(self, path=None, notindexed=True):
274 274 return util.makedir(self.join(path), notindexed)
275 275
276 276 def makedirs(self, path=None, mode=None):
277 277 return util.makedirs(self.join(path), mode)
278 278
279 279 def makelock(self, info, path):
280 280 return util.makelock(info, self.join(path))
281 281
282 282 def mkdir(self, path=None):
283 283 return os.mkdir(self.join(path))
284 284
285 285 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
286 286 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
287 287 dir=self.join(dir), text=text)
288 288 dname, fname = util.split(name)
289 289 if dir:
290 290 return fd, os.path.join(dir, fname)
291 291 else:
292 292 return fd, fname
293 293
294 294 def readdir(self, path=None, stat=None, skip=None):
295 295 return osutil.listdir(self.join(path), stat, skip)
296 296
297 297 def readlock(self, path):
298 298 return util.readlock(self.join(path))
299 299
300 300 def rename(self, src, dst):
301 301 return util.rename(self.join(src), self.join(dst))
302 302
303 303 def readlink(self, path):
304 304 return os.readlink(self.join(path))
305 305
306 306 def setflags(self, path, l, x):
307 307 return util.setflags(self.join(path), l, x)
308 308
309 309 def stat(self, path=None):
310 310 return os.stat(self.join(path))
311 311
312 312 def unlink(self, path=None):
313 313 return util.unlink(self.join(path))
314 314
315 315 def unlinkpath(self, path=None, ignoremissing=False):
316 316 return util.unlinkpath(self.join(path), ignoremissing)
317 317
318 318 def utime(self, path=None, t=None):
319 319 return os.utime(self.join(path), t)
320 320
321 321 class vfs(abstractvfs):
322 322 '''Operate files relative to a base directory
323 323
324 324 This class is used to hide the details of COW semantics and
325 325 remote file access from higher level code.
326 326 '''
327 327 def __init__(self, base, audit=True, expandpath=False, realpath=False):
328 328 if expandpath:
329 329 base = util.expandpath(base)
330 330 if realpath:
331 331 base = os.path.realpath(base)
332 332 self.base = base
333 333 self._setmustaudit(audit)
334 334 self.createmode = None
335 335 self._trustnlink = None
336 336
337 337 def _getmustaudit(self):
338 338 return self._audit
339 339
340 340 def _setmustaudit(self, onoff):
341 341 self._audit = onoff
342 342 if onoff:
343 343 self.audit = pathutil.pathauditor(self.base)
344 344 else:
345 345 self.audit = util.always
346 346
347 347 mustaudit = property(_getmustaudit, _setmustaudit)
348 348
349 349 @util.propertycache
350 350 def _cansymlink(self):
351 351 return util.checklink(self.base)
352 352
353 353 @util.propertycache
354 354 def _chmod(self):
355 355 return util.checkexec(self.base)
356 356
357 357 def _fixfilemode(self, name):
358 358 if self.createmode is None or not self._chmod:
359 359 return
360 360 os.chmod(name, self.createmode & 0666)
361 361
362 362 def __call__(self, path, mode="r", text=False, atomictemp=False,
363 363 notindexed=False):
364 364 '''Open ``path`` file, which is relative to vfs root.
365 365
366 366 Newly created directories are marked as "not to be indexed by
367 367 the content indexing service", if ``notindexed`` is specified
368 368 for "write" mode access.
369 369 '''
370 370 if self._audit:
371 371 r = util.checkosfilename(path)
372 372 if r:
373 373 raise util.Abort("%s: %r" % (r, path))
374 374 self.audit(path)
375 375 f = self.join(path)
376 376
377 377 if not text and "b" not in mode:
378 378 mode += "b" # for that other OS
379 379
380 380 nlink = -1
381 381 if mode not in ('r', 'rb'):
382 382 dirname, basename = util.split(f)
383 383 # If basename is empty, then the path is malformed because it points
384 384 # to a directory. Let the posixfile() call below raise IOError.
385 385 if basename:
386 386 if atomictemp:
387 387 util.ensuredirs(dirname, self.createmode, notindexed)
388 388 return util.atomictempfile(f, mode, self.createmode)
389 389 try:
390 390 if 'w' in mode:
391 391 util.unlink(f)
392 392 nlink = 0
393 393 else:
394 394 # nlinks() may behave differently for files on Windows
395 395 # shares if the file is open.
396 396 fd = util.posixfile(f)
397 397 nlink = util.nlinks(f)
398 398 if nlink < 1:
399 399 nlink = 2 # force mktempcopy (issue1922)
400 400 fd.close()
401 401 except (OSError, IOError), e:
402 402 if e.errno != errno.ENOENT:
403 403 raise
404 404 nlink = 0
405 405 util.ensuredirs(dirname, self.createmode, notindexed)
406 406 if nlink > 0:
407 407 if self._trustnlink is None:
408 408 self._trustnlink = nlink > 1 or util.checknlink(f)
409 409 if nlink > 1 or not self._trustnlink:
410 410 util.rename(util.mktempcopy(f), f)
411 411 fp = util.posixfile(f, mode)
412 412 if nlink == 0:
413 413 self._fixfilemode(f)
414 414 return fp
415 415
416 416 def symlink(self, src, dst):
417 417 self.audit(dst)
418 418 linkname = self.join(dst)
419 419 try:
420 420 os.unlink(linkname)
421 421 except OSError:
422 422 pass
423 423
424 424 util.ensuredirs(os.path.dirname(linkname), self.createmode)
425 425
426 426 if self._cansymlink:
427 427 try:
428 428 os.symlink(src, linkname)
429 429 except OSError, err:
430 430 raise OSError(err.errno, _('could not symlink to %r: %s') %
431 431 (src, err.strerror), linkname)
432 432 else:
433 433 self.write(dst, src)
434 434
435 435 def join(self, path):
436 436 if path:
437 437 return os.path.join(self.base, path)
438 438 else:
439 439 return self.base
440 440
441 441 opener = vfs
442 442
443 443 class auditvfs(object):
444 444 def __init__(self, vfs):
445 445 self.vfs = vfs
446 446
447 447 def _getmustaudit(self):
448 448 return self.vfs.mustaudit
449 449
450 450 def _setmustaudit(self, onoff):
451 451 self.vfs.mustaudit = onoff
452 452
453 453 mustaudit = property(_getmustaudit, _setmustaudit)
454 454
455 455 class filtervfs(abstractvfs, auditvfs):
456 456 '''Wrapper vfs for filtering filenames with a function.'''
457 457
458 458 def __init__(self, vfs, filter):
459 459 auditvfs.__init__(self, vfs)
460 460 self._filter = filter
461 461
462 462 def __call__(self, path, *args, **kwargs):
463 463 return self.vfs(self._filter(path), *args, **kwargs)
464 464
465 465 def join(self, path):
466 466 if path:
467 467 return self.vfs.join(self._filter(path))
468 468 else:
469 469 return self.vfs.join(path)
470 470
471 471 filteropener = filtervfs
472 472
473 473 class readonlyvfs(abstractvfs, auditvfs):
474 474 '''Wrapper vfs preventing any writing.'''
475 475
476 476 def __init__(self, vfs):
477 477 auditvfs.__init__(self, vfs)
478 478
479 479 def __call__(self, path, mode='r', *args, **kw):
480 480 if mode not in ('r', 'rb'):
481 481 raise util.Abort('this vfs is read only')
482 482 return self.vfs(path, mode, *args, **kw)
483 483
484 484
485 485 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
486 486 '''yield every hg repository under path, always recursively.
487 487 The recurse flag will only control recursion into repo working dirs'''
488 488 def errhandler(err):
489 489 if err.filename == path:
490 490 raise err
491 491 samestat = getattr(os.path, 'samestat', None)
492 492 if followsym and samestat is not None:
493 493 def adddir(dirlst, dirname):
494 494 match = False
495 495 dirstat = os.stat(dirname)
496 496 for lstdirstat in dirlst:
497 497 if samestat(dirstat, lstdirstat):
498 498 match = True
499 499 break
500 500 if not match:
501 501 dirlst.append(dirstat)
502 502 return not match
503 503 else:
504 504 followsym = False
505 505
506 506 if (seen_dirs is None) and followsym:
507 507 seen_dirs = []
508 508 adddir(seen_dirs, path)
509 509 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
510 510 dirs.sort()
511 511 if '.hg' in dirs:
512 512 yield root # found a repository
513 513 qroot = os.path.join(root, '.hg', 'patches')
514 514 if os.path.isdir(os.path.join(qroot, '.hg')):
515 515 yield qroot # we have a patch queue repo here
516 516 if recurse:
517 517 # avoid recursing inside the .hg directory
518 518 dirs.remove('.hg')
519 519 else:
520 520 dirs[:] = [] # don't descend further
521 521 elif followsym:
522 522 newdirs = []
523 523 for d in dirs:
524 524 fname = os.path.join(root, d)
525 525 if adddir(seen_dirs, fname):
526 526 if os.path.islink(fname):
527 527 for hgname in walkrepos(fname, True, seen_dirs):
528 528 yield hgname
529 529 else:
530 530 newdirs.append(d)
531 531 dirs[:] = newdirs
532 532
533 533 def osrcpath():
534 534 '''return default os-specific hgrc search path'''
535 535 path = []
536 536 defaultpath = os.path.join(util.datapath, 'default.d')
537 537 if os.path.isdir(defaultpath):
538 538 for f, kind in osutil.listdir(defaultpath):
539 539 if f.endswith('.rc'):
540 540 path.append(os.path.join(defaultpath, f))
541 541 path.extend(systemrcpath())
542 542 path.extend(userrcpath())
543 543 path = [os.path.normpath(f) for f in path]
544 544 return path
545 545
546 546 _rcpath = None
547 547
548 548 def rcpath():
549 549 '''return hgrc search path. if env var HGRCPATH is set, use it.
550 550 for each item in path, if directory, use files ending in .rc,
551 551 else use item.
552 552 make HGRCPATH empty to only look in .hg/hgrc of current repo.
553 553 if no HGRCPATH, use default os-specific path.'''
554 554 global _rcpath
555 555 if _rcpath is None:
556 556 if 'HGRCPATH' in os.environ:
557 557 _rcpath = []
558 558 for p in os.environ['HGRCPATH'].split(os.pathsep):
559 559 if not p:
560 560 continue
561 561 p = util.expandpath(p)
562 562 if os.path.isdir(p):
563 563 for f, kind in osutil.listdir(p):
564 564 if f.endswith('.rc'):
565 565 _rcpath.append(os.path.join(p, f))
566 566 else:
567 567 _rcpath.append(p)
568 568 else:
569 569 _rcpath = osrcpath()
570 570 return _rcpath
571 571
572 572 def revsingle(repo, revspec, default='.'):
573 573 if not revspec and revspec != 0:
574 574 return repo[default]
575 575
576 576 l = revrange(repo, [revspec])
577 577 if not l:
578 578 raise util.Abort(_('empty revision set'))
579 579 return repo[l.last()]
580 580
581 581 def revpair(repo, revs):
582 582 if not revs:
583 583 return repo.dirstate.p1(), None
584 584
585 585 l = revrange(repo, revs)
586 586
587 587 if not l:
588 588 first = second = None
589 589 elif l.isascending():
590 590 first = l.min()
591 591 second = l.max()
592 592 elif l.isdescending():
593 593 first = l.max()
594 594 second = l.min()
595 595 else:
596 596 first = l.first()
597 597 second = l.last()
598 598
599 599 if first is None:
600 600 raise util.Abort(_('empty revision range'))
601 601
602 602 if first == second and len(revs) == 1 and _revrangesep not in revs[0]:
603 603 return repo.lookup(first), None
604 604
605 605 return repo.lookup(first), repo.lookup(second)
606 606
607 607 _revrangesep = ':'
608 608
609 609 def revrange(repo, revs):
610 610 """Yield revision as strings from a list of revision specifications."""
611 611
612 612 def revfix(repo, val, defval):
613 613 if not val and val != 0 and defval is not None:
614 614 return defval
615 615 return repo[val].rev()
616 616
617 617 seen, l = set(), revset.baseset([])
618 618 for spec in revs:
619 619 if l and not seen:
620 620 seen = set(l)
621 621 # attempt to parse old-style ranges first to deal with
622 622 # things like old-tag which contain query metacharacters
623 623 try:
624 624 if isinstance(spec, int):
625 625 seen.add(spec)
626 626 l = l + revset.baseset([spec])
627 627 continue
628 628
629 629 if _revrangesep in spec:
630 630 start, end = spec.split(_revrangesep, 1)
631 631 start = revfix(repo, start, 0)
632 632 end = revfix(repo, end, len(repo) - 1)
633 633 if end == nullrev and start < 0:
634 634 start = nullrev
635 635 rangeiter = repo.changelog.revs(start, end)
636 636 if not seen and not l:
637 637 # by far the most common case: revs = ["-1:0"]
638 638 l = revset.baseset(rangeiter)
639 639 # defer syncing seen until next iteration
640 640 continue
641 641 newrevs = set(rangeiter)
642 642 if seen:
643 643 newrevs.difference_update(seen)
644 644 seen.update(newrevs)
645 645 else:
646 646 seen = newrevs
647 647 l = l + revset.baseset(sorted(newrevs, reverse=start > end))
648 648 continue
649 649 elif spec and spec in repo: # single unquoted rev
650 650 rev = revfix(repo, spec, None)
651 651 if rev in seen:
652 652 continue
653 653 seen.add(rev)
654 654 l = l + revset.baseset([rev])
655 655 continue
656 656 except error.RepoLookupError:
657 657 pass
658 658
659 659 # fall through to new-style queries if old-style fails
660 660 m = revset.match(repo.ui, spec, repo)
661 661 if seen or l:
662 662 dl = [r for r in m(repo, revset.spanset(repo)) if r not in seen]
663 663 l = l + revset.baseset(dl)
664 664 seen.update(dl)
665 665 else:
666 666 l = m(repo, revset.spanset(repo))
667 667
668 668 return l
669 669
670 670 def expandpats(pats):
671 671 '''Expand bare globs when running on windows.
672 672 On posix we assume it already has already been done by sh.'''
673 673 if not util.expandglobs:
674 674 return list(pats)
675 675 ret = []
676 676 for kindpat in pats:
677 677 kind, pat = matchmod._patsplit(kindpat, None)
678 678 if kind is None:
679 679 try:
680 680 globbed = glob.glob(pat)
681 681 except re.error:
682 682 globbed = [pat]
683 683 if globbed:
684 684 ret.extend(globbed)
685 685 continue
686 686 ret.append(kindpat)
687 687 return ret
688 688
689 689 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
690 690 '''Return a matcher and the patterns that were used.
691 691 The matcher will warn about bad matches.'''
692 692 if pats == ("",):
693 693 pats = []
694 694 if not globbed and default == 'relpath':
695 695 pats = expandpats(pats or [])
696 696
697 697 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
698 698 default)
699 699 def badfn(f, msg):
700 700 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
701 701 m.bad = badfn
702 702 return m, pats
703 703
704 704 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
705 705 '''Return a matcher that will warn about bad matches.'''
706 706 return matchandpats(ctx, pats, opts, globbed, default)[0]
707 707
708 708 def matchall(repo):
709 709 '''Return a matcher that will efficiently match everything.'''
710 710 return matchmod.always(repo.root, repo.getcwd())
711 711
712 712 def matchfiles(repo, files):
713 713 '''Return a matcher that will efficiently match exactly these files.'''
714 714 return matchmod.exact(repo.root, repo.getcwd(), files)
715 715
716 716 def addremove(repo, matcher, prefix, opts={}, dry_run=None, similarity=None):
717 717 m = matcher
718 718 if dry_run is None:
719 719 dry_run = opts.get('dry_run')
720 720 if similarity is None:
721 721 similarity = float(opts.get('similarity') or 0)
722 722
723 723 ret = 0
724 724 join = lambda f: os.path.join(prefix, f)
725 725
726 def matchessubrepo(matcher, subpath):
727 if matcher.exact(subpath):
728 return True
729 for f in matcher.files():
730 if f.startswith(subpath):
731 return True
732 return False
733
726 734 wctx = repo[None]
727 735 for subpath in sorted(wctx.substate):
728 if opts.get('subrepos'):
736 if opts.get('subrepos') or matchessubrepo(m, subpath):
729 737 sub = wctx.sub(subpath)
730 738 try:
731 739 submatch = matchmod.narrowmatcher(subpath, m)
732 740 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
733 741 ret = 1
734 742 except error.LookupError:
735 743 repo.ui.status(_("skipping missing subrepository: %s\n")
736 744 % join(subpath))
737 745
738 746 rejected = []
739 747 origbad = m.bad
740 748 def badfn(f, msg):
741 749 if f in m.files():
742 750 origbad(f, msg)
743 751 rejected.append(f)
744 752
745 753 m.bad = badfn
746 754 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
747 755 m.bad = origbad
748 756
749 757 unknownset = set(unknown + forgotten)
750 758 toprint = unknownset.copy()
751 759 toprint.update(deleted)
752 760 for abs in sorted(toprint):
753 761 if repo.ui.verbose or not m.exact(abs):
754 762 if abs in unknownset:
755 763 status = _('adding %s\n') % m.uipath(join(abs))
756 764 else:
757 765 status = _('removing %s\n') % m.uipath(join(abs))
758 766 repo.ui.status(status)
759 767
760 768 renames = _findrenames(repo, m, added + unknown, removed + deleted,
761 769 similarity)
762 770
763 771 if not dry_run:
764 772 _markchanges(repo, unknown + forgotten, deleted, renames)
765 773
766 774 for f in rejected:
767 775 if f in m.files():
768 776 return 1
769 777 return ret
770 778
771 779 def marktouched(repo, files, similarity=0.0):
772 780 '''Assert that files have somehow been operated upon. files are relative to
773 781 the repo root.'''
774 782 m = matchfiles(repo, files)
775 783 rejected = []
776 784 m.bad = lambda x, y: rejected.append(x)
777 785
778 786 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
779 787
780 788 if repo.ui.verbose:
781 789 unknownset = set(unknown + forgotten)
782 790 toprint = unknownset.copy()
783 791 toprint.update(deleted)
784 792 for abs in sorted(toprint):
785 793 if abs in unknownset:
786 794 status = _('adding %s\n') % abs
787 795 else:
788 796 status = _('removing %s\n') % abs
789 797 repo.ui.status(status)
790 798
791 799 renames = _findrenames(repo, m, added + unknown, removed + deleted,
792 800 similarity)
793 801
794 802 _markchanges(repo, unknown + forgotten, deleted, renames)
795 803
796 804 for f in rejected:
797 805 if f in m.files():
798 806 return 1
799 807 return 0
800 808
801 809 def _interestingfiles(repo, matcher):
802 810 '''Walk dirstate with matcher, looking for files that addremove would care
803 811 about.
804 812
805 813 This is different from dirstate.status because it doesn't care about
806 814 whether files are modified or clean.'''
807 815 added, unknown, deleted, removed, forgotten = [], [], [], [], []
808 816 audit_path = pathutil.pathauditor(repo.root)
809 817
810 818 ctx = repo[None]
811 819 dirstate = repo.dirstate
812 820 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
813 821 full=False)
814 822 for abs, st in walkresults.iteritems():
815 823 dstate = dirstate[abs]
816 824 if dstate == '?' and audit_path.check(abs):
817 825 unknown.append(abs)
818 826 elif dstate != 'r' and not st:
819 827 deleted.append(abs)
820 828 elif dstate == 'r' and st:
821 829 forgotten.append(abs)
822 830 # for finding renames
823 831 elif dstate == 'r' and not st:
824 832 removed.append(abs)
825 833 elif dstate == 'a':
826 834 added.append(abs)
827 835
828 836 return added, unknown, deleted, removed, forgotten
829 837
830 838 def _findrenames(repo, matcher, added, removed, similarity):
831 839 '''Find renames from removed files to added ones.'''
832 840 renames = {}
833 841 if similarity > 0:
834 842 for old, new, score in similar.findrenames(repo, added, removed,
835 843 similarity):
836 844 if (repo.ui.verbose or not matcher.exact(old)
837 845 or not matcher.exact(new)):
838 846 repo.ui.status(_('recording removal of %s as rename to %s '
839 847 '(%d%% similar)\n') %
840 848 (matcher.rel(old), matcher.rel(new),
841 849 score * 100))
842 850 renames[new] = old
843 851 return renames
844 852
845 853 def _markchanges(repo, unknown, deleted, renames):
846 854 '''Marks the files in unknown as added, the files in deleted as removed,
847 855 and the files in renames as copied.'''
848 856 wctx = repo[None]
849 857 wlock = repo.wlock()
850 858 try:
851 859 wctx.forget(deleted)
852 860 wctx.add(unknown)
853 861 for new, old in renames.iteritems():
854 862 wctx.copy(old, new)
855 863 finally:
856 864 wlock.release()
857 865
858 866 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
859 867 """Update the dirstate to reflect the intent of copying src to dst. For
860 868 different reasons it might not end with dst being marked as copied from src.
861 869 """
862 870 origsrc = repo.dirstate.copied(src) or src
863 871 if dst == origsrc: # copying back a copy?
864 872 if repo.dirstate[dst] not in 'mn' and not dryrun:
865 873 repo.dirstate.normallookup(dst)
866 874 else:
867 875 if repo.dirstate[origsrc] == 'a' and origsrc == src:
868 876 if not ui.quiet:
869 877 ui.warn(_("%s has not been committed yet, so no copy "
870 878 "data will be stored for %s.\n")
871 879 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
872 880 if repo.dirstate[dst] in '?r' and not dryrun:
873 881 wctx.add([dst])
874 882 elif not dryrun:
875 883 wctx.copy(origsrc, dst)
876 884
877 885 def readrequires(opener, supported):
878 886 '''Reads and parses .hg/requires and checks if all entries found
879 887 are in the list of supported features.'''
880 888 requirements = set(opener.read("requires").splitlines())
881 889 missings = []
882 890 for r in requirements:
883 891 if r not in supported:
884 892 if not r or not r[0].isalnum():
885 893 raise error.RequirementError(_(".hg/requires file is corrupt"))
886 894 missings.append(r)
887 895 missings.sort()
888 896 if missings:
889 897 raise error.RequirementError(
890 898 _("repository requires features unknown to this Mercurial: %s")
891 899 % " ".join(missings),
892 900 hint=_("see http://mercurial.selenic.com/wiki/MissingRequirement"
893 901 " for more information"))
894 902 return requirements
895 903
896 904 class filecachesubentry(object):
897 905 def __init__(self, path, stat):
898 906 self.path = path
899 907 self.cachestat = None
900 908 self._cacheable = None
901 909
902 910 if stat:
903 911 self.cachestat = filecachesubentry.stat(self.path)
904 912
905 913 if self.cachestat:
906 914 self._cacheable = self.cachestat.cacheable()
907 915 else:
908 916 # None means we don't know yet
909 917 self._cacheable = None
910 918
911 919 def refresh(self):
912 920 if self.cacheable():
913 921 self.cachestat = filecachesubentry.stat(self.path)
914 922
915 923 def cacheable(self):
916 924 if self._cacheable is not None:
917 925 return self._cacheable
918 926
919 927 # we don't know yet, assume it is for now
920 928 return True
921 929
922 930 def changed(self):
923 931 # no point in going further if we can't cache it
924 932 if not self.cacheable():
925 933 return True
926 934
927 935 newstat = filecachesubentry.stat(self.path)
928 936
929 937 # we may not know if it's cacheable yet, check again now
930 938 if newstat and self._cacheable is None:
931 939 self._cacheable = newstat.cacheable()
932 940
933 941 # check again
934 942 if not self._cacheable:
935 943 return True
936 944
937 945 if self.cachestat != newstat:
938 946 self.cachestat = newstat
939 947 return True
940 948 else:
941 949 return False
942 950
943 951 @staticmethod
944 952 def stat(path):
945 953 try:
946 954 return util.cachestat(path)
947 955 except OSError, e:
948 956 if e.errno != errno.ENOENT:
949 957 raise
950 958
951 959 class filecacheentry(object):
952 960 def __init__(self, paths, stat=True):
953 961 self._entries = []
954 962 for path in paths:
955 963 self._entries.append(filecachesubentry(path, stat))
956 964
957 965 def changed(self):
958 966 '''true if any entry has changed'''
959 967 for entry in self._entries:
960 968 if entry.changed():
961 969 return True
962 970 return False
963 971
964 972 def refresh(self):
965 973 for entry in self._entries:
966 974 entry.refresh()
967 975
968 976 class filecache(object):
969 977 '''A property like decorator that tracks files under .hg/ for updates.
970 978
971 979 Records stat info when called in _filecache.
972 980
973 981 On subsequent calls, compares old stat info with new info, and recreates the
974 982 object when any of the files changes, updating the new stat info in
975 983 _filecache.
976 984
977 985 Mercurial either atomic renames or appends for files under .hg,
978 986 so to ensure the cache is reliable we need the filesystem to be able
979 987 to tell us if a file has been replaced. If it can't, we fallback to
980 988 recreating the object on every call (essentially the same behaviour as
981 989 propertycache).
982 990
983 991 '''
984 992 def __init__(self, *paths):
985 993 self.paths = paths
986 994
987 995 def join(self, obj, fname):
988 996 """Used to compute the runtime path of a cached file.
989 997
990 998 Users should subclass filecache and provide their own version of this
991 999 function to call the appropriate join function on 'obj' (an instance
992 1000 of the class that its member function was decorated).
993 1001 """
994 1002 return obj.join(fname)
995 1003
996 1004 def __call__(self, func):
997 1005 self.func = func
998 1006 self.name = func.__name__
999 1007 return self
1000 1008
1001 1009 def __get__(self, obj, type=None):
1002 1010 # do we need to check if the file changed?
1003 1011 if self.name in obj.__dict__:
1004 1012 assert self.name in obj._filecache, self.name
1005 1013 return obj.__dict__[self.name]
1006 1014
1007 1015 entry = obj._filecache.get(self.name)
1008 1016
1009 1017 if entry:
1010 1018 if entry.changed():
1011 1019 entry.obj = self.func(obj)
1012 1020 else:
1013 1021 paths = [self.join(obj, path) for path in self.paths]
1014 1022
1015 1023 # We stat -before- creating the object so our cache doesn't lie if
1016 1024 # a writer modified between the time we read and stat
1017 1025 entry = filecacheentry(paths, True)
1018 1026 entry.obj = self.func(obj)
1019 1027
1020 1028 obj._filecache[self.name] = entry
1021 1029
1022 1030 obj.__dict__[self.name] = entry.obj
1023 1031 return entry.obj
1024 1032
1025 1033 def __set__(self, obj, value):
1026 1034 if self.name not in obj._filecache:
1027 1035 # we add an entry for the missing value because X in __dict__
1028 1036 # implies X in _filecache
1029 1037 paths = [self.join(obj, path) for path in self.paths]
1030 1038 ce = filecacheentry(paths, False)
1031 1039 obj._filecache[self.name] = ce
1032 1040 else:
1033 1041 ce = obj._filecache[self.name]
1034 1042
1035 1043 ce.obj = value # update cached copy
1036 1044 obj.__dict__[self.name] = value # update copy returned by obj.x
1037 1045
1038 1046 def __delete__(self, obj):
1039 1047 try:
1040 1048 del obj.__dict__[self.name]
1041 1049 except KeyError:
1042 1050 raise AttributeError(self.name)
1043 1051
1044 1052 class dirs(object):
1045 1053 '''a multiset of directory names from a dirstate or manifest'''
1046 1054
1047 1055 def __init__(self, map, skip=None):
1048 1056 self._dirs = {}
1049 1057 addpath = self.addpath
1050 1058 if util.safehasattr(map, 'iteritems') and skip is not None:
1051 1059 for f, s in map.iteritems():
1052 1060 if s[0] != skip:
1053 1061 addpath(f)
1054 1062 else:
1055 1063 for f in map:
1056 1064 addpath(f)
1057 1065
1058 1066 def addpath(self, path):
1059 1067 dirs = self._dirs
1060 1068 for base in finddirs(path):
1061 1069 if base in dirs:
1062 1070 dirs[base] += 1
1063 1071 return
1064 1072 dirs[base] = 1
1065 1073
1066 1074 def delpath(self, path):
1067 1075 dirs = self._dirs
1068 1076 for base in finddirs(path):
1069 1077 if dirs[base] > 1:
1070 1078 dirs[base] -= 1
1071 1079 return
1072 1080 del dirs[base]
1073 1081
1074 1082 def __iter__(self):
1075 1083 return self._dirs.iterkeys()
1076 1084
1077 1085 def __contains__(self, d):
1078 1086 return d in self._dirs
1079 1087
1080 1088 if util.safehasattr(parsers, 'dirs'):
1081 1089 dirs = parsers.dirs
1082 1090
1083 1091 def finddirs(path):
1084 1092 pos = path.rfind('/')
1085 1093 while pos != -1:
1086 1094 yield path[:pos]
1087 1095 pos = path.rfind('/', 0, pos)
@@ -1,313 +1,322 b''
1 1 Preparing the subrepository 'sub2'
2 2
3 3 $ hg init sub2
4 4 $ echo sub2 > sub2/sub2
5 5 $ hg add -R sub2
6 6 adding sub2/sub2 (glob)
7 7 $ hg commit -R sub2 -m "sub2 import"
8 8
9 9 Preparing the 'sub1' repo which depends on the subrepo 'sub2'
10 10
11 11 $ hg init sub1
12 12 $ echo sub1 > sub1/sub1
13 13 $ echo "sub2 = ../sub2" > sub1/.hgsub
14 14 $ hg clone sub2 sub1/sub2
15 15 updating to branch default
16 16 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
17 17 $ hg add -R sub1
18 18 adding sub1/.hgsub (glob)
19 19 adding sub1/sub1 (glob)
20 20 $ hg commit -R sub1 -m "sub1 import"
21 21
22 22 Preparing the 'main' repo which depends on the subrepo 'sub1'
23 23
24 24 $ hg init main
25 25 $ echo main > main/main
26 26 $ echo "sub1 = ../sub1" > main/.hgsub
27 27 $ hg clone sub1 main/sub1
28 28 updating to branch default
29 29 cloning subrepo sub2 from $TESTTMP/sub2
30 30 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
31 31 $ hg add -R main
32 32 adding main/.hgsub (glob)
33 33 adding main/main (glob)
34 34 $ hg commit -R main -m "main import"
35 35
36 36 Cleaning both repositories, just as a clone -U
37 37
38 38 $ hg up -C -R sub2 null
39 39 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
40 40 $ hg up -C -R sub1 null
41 41 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
42 42 $ hg up -C -R main null
43 43 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
44 44 $ rm -rf main/sub1
45 45 $ rm -rf sub1/sub2
46 46
47 47 Clone main
48 48
49 49 $ hg clone main cloned
50 50 updating to branch default
51 51 cloning subrepo sub1 from $TESTTMP/sub1
52 52 cloning subrepo sub1/sub2 from $TESTTMP/sub2 (glob)
53 53 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 54
55 55 Checking cloned repo ids
56 56
57 57 $ printf "cloned " ; hg id -R cloned
58 58 cloned 7f491f53a367 tip
59 59 $ printf "cloned/sub1 " ; hg id -R cloned/sub1
60 60 cloned/sub1 fc3b4ce2696f tip
61 61 $ printf "cloned/sub1/sub2 " ; hg id -R cloned/sub1/sub2
62 62 cloned/sub1/sub2 c57a0840e3ba tip
63 63
64 64 debugsub output for main and sub1
65 65
66 66 $ hg debugsub -R cloned
67 67 path sub1
68 68 source ../sub1
69 69 revision fc3b4ce2696f7741438c79207583768f2ce6b0dd
70 70 $ hg debugsub -R cloned/sub1
71 71 path sub2
72 72 source ../sub2
73 73 revision c57a0840e3badd667ef3c3ef65471609acb2ba3c
74 74
75 75 Modifying deeply nested 'sub2'
76 76
77 77 $ echo modified > cloned/sub1/sub2/sub2
78 78 $ hg commit --subrepos -m "deep nested modif should trigger a commit" -R cloned
79 79 committing subrepository sub1
80 80 committing subrepository sub1/sub2 (glob)
81 81
82 82 Checking modified node ids
83 83
84 84 $ printf "cloned " ; hg id -R cloned
85 85 cloned ffe6649062fe tip
86 86 $ printf "cloned/sub1 " ; hg id -R cloned/sub1
87 87 cloned/sub1 2ecb03bf44a9 tip
88 88 $ printf "cloned/sub1/sub2 " ; hg id -R cloned/sub1/sub2
89 89 cloned/sub1/sub2 53dd3430bcaf tip
90 90
91 91 debugsub output for main and sub1
92 92
93 93 $ hg debugsub -R cloned
94 94 path sub1
95 95 source ../sub1
96 96 revision 2ecb03bf44a94e749e8669481dd9069526ce7cb9
97 97 $ hg debugsub -R cloned/sub1
98 98 path sub2
99 99 source ../sub2
100 100 revision 53dd3430bcaf5ab4a7c48262bcad6d441f510487
101 101
102 102 Check that deep archiving works
103 103
104 104 $ cd cloned
105 105 $ echo 'test' > sub1/sub2/test.txt
106 106 $ hg --config extensions.largefiles=! add sub1/sub2/test.txt
107 107 $ mkdir sub1/sub2/folder
108 108 $ echo 'subfolder' > sub1/sub2/folder/test.txt
109 109 $ hg ci -ASm "add test.txt"
110 110 adding sub1/sub2/folder/test.txt (glob)
111 111 committing subrepository sub1
112 112 committing subrepository sub1/sub2 (glob)
113 113
114 114 .. but first take a detour through some deep removal testing
115 115
116 116 $ hg remove -S -I 're:.*.txt' .
117 117 removing sub1/sub2/folder/test.txt (glob)
118 118 removing sub1/sub2/test.txt (glob)
119 119 $ hg status -S
120 120 R sub1/sub2/folder/test.txt
121 121 R sub1/sub2/test.txt
122 122 $ hg update -Cq
123 123 $ hg remove -I 're:.*.txt' sub1
124 124 $ hg status -S
125 125 $ hg remove sub1/sub2/folder/test.txt
126 126 $ hg remove sub1/.hgsubstate
127 127 $ hg status -S
128 128 R sub1/.hgsubstate
129 129 R sub1/sub2/folder/test.txt
130 130 $ hg update -Cq
131 131 $ touch sub1/foo
132 132 $ hg forget sub1/sub2/folder/test.txt
133 133 $ rm sub1/sub2/test.txt
134 134
135 135 Test relative path printing + subrepos
136 136 $ mkdir -p foo/bar
137 137 $ cd foo
138 138 $ touch bar/abc
139 139 $ hg addremove -S ..
140 140 adding ../sub1/sub2/folder/test.txt (glob)
141 141 removing ../sub1/sub2/test.txt (glob)
142 142 adding ../sub1/foo (glob)
143 143 adding bar/abc (glob)
144 144 $ cd ..
145 145 $ hg status -S
146 146 A foo/bar/abc
147 147 A sub1/foo
148 148 R sub1/sub2/test.txt
149 149 $ hg update -Cq
150 $ touch sub1/sub2/folder/bar
151 $ hg addremove sub1/sub2
152 adding sub1/sub2/folder/bar (glob)
153 $ hg status -S
154 A sub1/sub2/folder/bar
155 ? foo/bar/abc
156 ? sub1/foo
157 $ hg update -Cq
150 158 $ rm sub1/sub2/folder/test.txt
151 159 $ rm sub1/sub2/test.txt
152 160 $ hg ci -ASm "remove test.txt"
161 adding sub1/sub2/folder/bar (glob)
153 162 removing sub1/sub2/folder/test.txt (glob)
154 163 removing sub1/sub2/test.txt (glob)
155 164 adding sub1/foo (glob)
156 165 adding foo/bar/abc
157 166 committing subrepository sub1
158 167 committing subrepository sub1/sub2 (glob)
159 168 $ hg rollback -q
160 169 $ hg up -Cq
161 170
162 171 $ hg --config extensions.largefiles=! archive -S ../archive_all
163 172 $ find ../archive_all | sort
164 173 ../archive_all
165 174 ../archive_all/.hg_archival.txt
166 175 ../archive_all/.hgsub
167 176 ../archive_all/.hgsubstate
168 177 ../archive_all/main
169 178 ../archive_all/sub1
170 179 ../archive_all/sub1/.hgsub
171 180 ../archive_all/sub1/.hgsubstate
172 181 ../archive_all/sub1/sub1
173 182 ../archive_all/sub1/sub2
174 183 ../archive_all/sub1/sub2/folder
175 184 ../archive_all/sub1/sub2/folder/test.txt
176 185 ../archive_all/sub1/sub2/sub2
177 186 ../archive_all/sub1/sub2/test.txt
178 187
179 188 Check that archive -X works in deep subrepos
180 189
181 190 $ hg --config extensions.largefiles=! archive -S -X '**test*' ../archive_exclude
182 191 $ find ../archive_exclude | sort
183 192 ../archive_exclude
184 193 ../archive_exclude/.hg_archival.txt
185 194 ../archive_exclude/.hgsub
186 195 ../archive_exclude/.hgsubstate
187 196 ../archive_exclude/main
188 197 ../archive_exclude/sub1
189 198 ../archive_exclude/sub1/.hgsub
190 199 ../archive_exclude/sub1/.hgsubstate
191 200 ../archive_exclude/sub1/sub1
192 201 ../archive_exclude/sub1/sub2
193 202 ../archive_exclude/sub1/sub2/sub2
194 203
195 204 $ hg --config extensions.largefiles=! archive -S -I '**test*' ../archive_include
196 205 $ find ../archive_include | sort
197 206 ../archive_include
198 207 ../archive_include/sub1
199 208 ../archive_include/sub1/sub2
200 209 ../archive_include/sub1/sub2/folder
201 210 ../archive_include/sub1/sub2/folder/test.txt
202 211 ../archive_include/sub1/sub2/test.txt
203 212
204 213 Check that deep archive works with largefiles (which overrides hgsubrepo impl)
205 214 This also tests the repo.ui regression in 43fb170a23bd, and that lf subrepo
206 215 subrepos are archived properly.
207 216 Note that add --large through a subrepo currently adds the file as a normal file
208 217
209 218 $ echo "large" > sub1/sub2/large.bin
210 219 $ hg --config extensions.largefiles= add --large -R sub1/sub2 sub1/sub2/large.bin
211 220 $ echo "large" > large.bin
212 221 $ hg --config extensions.largefiles= add --large large.bin
213 222 $ hg --config extensions.largefiles= ci -S -m "add large files"
214 223 committing subrepository sub1
215 224 committing subrepository sub1/sub2 (glob)
216 225
217 226 $ hg --config extensions.largefiles= archive -S ../archive_lf
218 227 $ find ../archive_lf | sort
219 228 ../archive_lf
220 229 ../archive_lf/.hg_archival.txt
221 230 ../archive_lf/.hgsub
222 231 ../archive_lf/.hgsubstate
223 232 ../archive_lf/large.bin
224 233 ../archive_lf/main
225 234 ../archive_lf/sub1
226 235 ../archive_lf/sub1/.hgsub
227 236 ../archive_lf/sub1/.hgsubstate
228 237 ../archive_lf/sub1/sub1
229 238 ../archive_lf/sub1/sub2
230 239 ../archive_lf/sub1/sub2/folder
231 240 ../archive_lf/sub1/sub2/folder/test.txt
232 241 ../archive_lf/sub1/sub2/large.bin
233 242 ../archive_lf/sub1/sub2/sub2
234 243 ../archive_lf/sub1/sub2/test.txt
235 244 $ rm -rf ../archive_lf
236 245
237 246 Exclude large files from main and sub-sub repo
238 247
239 248 $ hg --config extensions.largefiles= archive -S -X '**.bin' ../archive_lf
240 249 $ find ../archive_lf | sort
241 250 ../archive_lf
242 251 ../archive_lf/.hg_archival.txt
243 252 ../archive_lf/.hgsub
244 253 ../archive_lf/.hgsubstate
245 254 ../archive_lf/main
246 255 ../archive_lf/sub1
247 256 ../archive_lf/sub1/.hgsub
248 257 ../archive_lf/sub1/.hgsubstate
249 258 ../archive_lf/sub1/sub1
250 259 ../archive_lf/sub1/sub2
251 260 ../archive_lf/sub1/sub2/folder
252 261 ../archive_lf/sub1/sub2/folder/test.txt
253 262 ../archive_lf/sub1/sub2/sub2
254 263 ../archive_lf/sub1/sub2/test.txt
255 264 $ rm -rf ../archive_lf
256 265
257 266 Exclude normal files from main and sub-sub repo
258 267
259 268 $ hg --config extensions.largefiles= archive -S -X '**.txt' ../archive_lf
260 269 $ find ../archive_lf | sort
261 270 ../archive_lf
262 271 ../archive_lf/.hgsub
263 272 ../archive_lf/.hgsubstate
264 273 ../archive_lf/large.bin
265 274 ../archive_lf/main
266 275 ../archive_lf/sub1
267 276 ../archive_lf/sub1/.hgsub
268 277 ../archive_lf/sub1/.hgsubstate
269 278 ../archive_lf/sub1/sub1
270 279 ../archive_lf/sub1/sub2
271 280 ../archive_lf/sub1/sub2/large.bin
272 281 ../archive_lf/sub1/sub2/sub2
273 282 $ rm -rf ../archive_lf
274 283
275 284 Include normal files from within a largefiles subrepo
276 285
277 286 $ hg --config extensions.largefiles= archive -S -I '**.txt' ../archive_lf
278 287 $ find ../archive_lf | sort
279 288 ../archive_lf
280 289 ../archive_lf/.hg_archival.txt
281 290 ../archive_lf/sub1
282 291 ../archive_lf/sub1/sub2
283 292 ../archive_lf/sub1/sub2/folder
284 293 ../archive_lf/sub1/sub2/folder/test.txt
285 294 ../archive_lf/sub1/sub2/test.txt
286 295 $ rm -rf ../archive_lf
287 296
288 297 Include large files from within a largefiles subrepo
289 298
290 299 $ hg --config extensions.largefiles= archive -S -I '**.bin' ../archive_lf
291 300 $ find ../archive_lf | sort
292 301 ../archive_lf
293 302 ../archive_lf/large.bin
294 303 ../archive_lf/sub1
295 304 ../archive_lf/sub1/sub2
296 305 ../archive_lf/sub1/sub2/large.bin
297 306 $ rm -rf ../archive_lf
298 307
299 308 Find an exact largefile match in a largefiles subrepo
300 309
301 310 $ hg --config extensions.largefiles= archive -S -I 'sub1/sub2/large.bin' ../archive_lf
302 311 $ find ../archive_lf | sort
303 312 ../archive_lf
304 313 ../archive_lf/sub1
305 314 ../archive_lf/sub1/sub2
306 315 ../archive_lf/sub1/sub2/large.bin
307 316 $ rm -rf ../archive_lf
308 317
309 318 Find an exact match to a standin (should archive nothing)
310 319 $ hg --config extensions.largefiles= archive -S -I 'sub/sub2/.hglf/large.bin' ../archive_lf
311 320 $ find ../archive_lf 2> /dev/null | sort
312 321
313 322 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now