##// END OF EJS Templates
lfs: consult the narrow matcher when extracting pointers from ctx (issue5794)...
Matt Harbison -
r40422:4a81d824 default
parent child Browse files
Show More
@@ -1,409 +1,414
1 1 # wrapper.py - methods wrapping core mercurial logic
2 2 #
3 3 # Copyright 2017 Facebook, Inc.
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 hashlib
11 11
12 12 from mercurial.i18n import _
13 13 from mercurial.node import bin, hex, nullid, short
14 14
15 15 from mercurial import (
16 16 error,
17 17 repository,
18 18 revlog,
19 19 util,
20 20 )
21 21
22 22 from mercurial.utils import (
23 23 storageutil,
24 24 stringutil,
25 25 )
26 26
27 27 from ..largefiles import lfutil
28 28
29 29 from . import (
30 30 blobstore,
31 31 pointer,
32 32 )
33 33
34 34 def localrepomakefilestorage(orig, requirements, features, **kwargs):
35 35 if b'lfs' in requirements:
36 36 features.add(repository.REPO_FEATURE_LFS)
37 37
38 38 return orig(requirements=requirements, features=features, **kwargs)
39 39
40 40 def allsupportedversions(orig, ui):
41 41 versions = orig(ui)
42 42 versions.add('03')
43 43 return versions
44 44
45 45 def _capabilities(orig, repo, proto):
46 46 '''Wrap server command to announce lfs server capability'''
47 47 caps = orig(repo, proto)
48 48 if util.safehasattr(repo.svfs, 'lfslocalblobstore'):
49 49 # Advertise a slightly different capability when lfs is *required*, so
50 50 # that the client knows it MUST load the extension. If lfs is not
51 51 # required on the server, there's no reason to autoload the extension
52 52 # on the client.
53 53 if b'lfs' in repo.requirements:
54 54 caps.append('lfs-serve')
55 55
56 56 caps.append('lfs')
57 57 return caps
58 58
59 59 def bypasscheckhash(self, text):
60 60 return False
61 61
62 62 def readfromstore(self, text):
63 63 """Read filelog content from local blobstore transform for flagprocessor.
64 64
65 65 Default tranform for flagprocessor, returning contents from blobstore.
66 66 Returns a 2-typle (text, validatehash) where validatehash is True as the
67 67 contents of the blobstore should be checked using checkhash.
68 68 """
69 69 p = pointer.deserialize(text)
70 70 oid = p.oid()
71 71 store = self.opener.lfslocalblobstore
72 72 if not store.has(oid):
73 73 p.filename = self.filename
74 74 self.opener.lfsremoteblobstore.readbatch([p], store)
75 75
76 76 # The caller will validate the content
77 77 text = store.read(oid, verify=False)
78 78
79 79 # pack hg filelog metadata
80 80 hgmeta = {}
81 81 for k in p.keys():
82 82 if k.startswith('x-hg-'):
83 83 name = k[len('x-hg-'):]
84 84 hgmeta[name] = p[k]
85 85 if hgmeta or text.startswith('\1\n'):
86 86 text = storageutil.packmeta(hgmeta, text)
87 87
88 88 return (text, True)
89 89
90 90 def writetostore(self, text):
91 91 # hg filelog metadata (includes rename, etc)
92 92 hgmeta, offset = storageutil.parsemeta(text)
93 93 if offset and offset > 0:
94 94 # lfs blob does not contain hg filelog metadata
95 95 text = text[offset:]
96 96
97 97 # git-lfs only supports sha256
98 98 oid = hex(hashlib.sha256(text).digest())
99 99 self.opener.lfslocalblobstore.write(oid, text)
100 100
101 101 # replace contents with metadata
102 102 longoid = 'sha256:%s' % oid
103 103 metadata = pointer.gitlfspointer(oid=longoid, size='%d' % len(text))
104 104
105 105 # by default, we expect the content to be binary. however, LFS could also
106 106 # be used for non-binary content. add a special entry for non-binary data.
107 107 # this will be used by filectx.isbinary().
108 108 if not stringutil.binary(text):
109 109 # not hg filelog metadata (affecting commit hash), no "x-hg-" prefix
110 110 metadata['x-is-binary'] = '0'
111 111
112 112 # translate hg filelog metadata to lfs metadata with "x-hg-" prefix
113 113 if hgmeta is not None:
114 114 for k, v in hgmeta.iteritems():
115 115 metadata['x-hg-%s' % k] = v
116 116
117 117 rawtext = metadata.serialize()
118 118 return (rawtext, False)
119 119
120 120 def _islfs(rlog, node=None, rev=None):
121 121 if rev is None:
122 122 if node is None:
123 123 # both None - likely working copy content where node is not ready
124 124 return False
125 125 rev = rlog._revlog.rev(node)
126 126 else:
127 127 node = rlog._revlog.node(rev)
128 128 if node == nullid:
129 129 return False
130 130 flags = rlog._revlog.flags(rev)
131 131 return bool(flags & revlog.REVIDX_EXTSTORED)
132 132
133 133 def filelogaddrevision(orig, self, text, transaction, link, p1, p2,
134 134 cachedelta=None, node=None,
135 135 flags=revlog.REVIDX_DEFAULT_FLAGS, **kwds):
136 136 # The matcher isn't available if reposetup() wasn't called.
137 137 lfstrack = self._revlog.opener.options.get('lfstrack')
138 138
139 139 if lfstrack:
140 140 textlen = len(text)
141 141 # exclude hg rename meta from file size
142 142 meta, offset = storageutil.parsemeta(text)
143 143 if offset:
144 144 textlen -= offset
145 145
146 146 if lfstrack(self._revlog.filename, textlen):
147 147 flags |= revlog.REVIDX_EXTSTORED
148 148
149 149 return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
150 150 node=node, flags=flags, **kwds)
151 151
152 152 def filelogrenamed(orig, self, node):
153 153 if _islfs(self, node):
154 154 rawtext = self._revlog.revision(node, raw=True)
155 155 if not rawtext:
156 156 return False
157 157 metadata = pointer.deserialize(rawtext)
158 158 if 'x-hg-copy' in metadata and 'x-hg-copyrev' in metadata:
159 159 return metadata['x-hg-copy'], bin(metadata['x-hg-copyrev'])
160 160 else:
161 161 return False
162 162 return orig(self, node)
163 163
164 164 def filelogsize(orig, self, rev):
165 165 if _islfs(self, rev=rev):
166 166 # fast path: use lfs metadata to answer size
167 167 rawtext = self._revlog.revision(rev, raw=True)
168 168 metadata = pointer.deserialize(rawtext)
169 169 return int(metadata['size'])
170 170 return orig(self, rev)
171 171
172 172 def filectxcmp(orig, self, fctx):
173 173 """returns True if text is different than fctx"""
174 174 # some fctx (ex. hg-git) is not based on basefilectx and do not have islfs
175 175 if self.islfs() and getattr(fctx, 'islfs', lambda: False)():
176 176 # fast path: check LFS oid
177 177 p1 = pointer.deserialize(self.rawdata())
178 178 p2 = pointer.deserialize(fctx.rawdata())
179 179 return p1.oid() != p2.oid()
180 180 return orig(self, fctx)
181 181
182 182 def filectxisbinary(orig, self):
183 183 if self.islfs():
184 184 # fast path: use lfs metadata to answer isbinary
185 185 metadata = pointer.deserialize(self.rawdata())
186 186 # if lfs metadata says nothing, assume it's binary by default
187 187 return bool(int(metadata.get('x-is-binary', 1)))
188 188 return orig(self)
189 189
190 190 def filectxislfs(self):
191 191 return _islfs(self.filelog(), self.filenode())
192 192
193 193 def _updatecatformatter(orig, fm, ctx, matcher, path, decode):
194 194 orig(fm, ctx, matcher, path, decode)
195 195 fm.data(rawdata=ctx[path].rawdata())
196 196
197 197 def convertsink(orig, sink):
198 198 sink = orig(sink)
199 199 if sink.repotype == 'hg':
200 200 class lfssink(sink.__class__):
201 201 def putcommit(self, files, copies, parents, commit, source, revmap,
202 202 full, cleanp2):
203 203 pc = super(lfssink, self).putcommit
204 204 node = pc(files, copies, parents, commit, source, revmap, full,
205 205 cleanp2)
206 206
207 207 if 'lfs' not in self.repo.requirements:
208 208 ctx = self.repo[node]
209 209
210 210 # The file list may contain removed files, so check for
211 211 # membership before assuming it is in the context.
212 212 if any(f in ctx and ctx[f].islfs() for f, n in files):
213 213 self.repo.requirements.add('lfs')
214 214 self.repo._writerequirements()
215 215
216 216 return node
217 217
218 218 sink.__class__ = lfssink
219 219
220 220 return sink
221 221
222 222 def vfsinit(orig, self, othervfs):
223 223 orig(self, othervfs)
224 224 # copy lfs related options
225 225 for k, v in othervfs.options.items():
226 226 if k.startswith('lfs'):
227 227 self.options[k] = v
228 228 # also copy lfs blobstores. note: this can run before reposetup, so lfs
229 229 # blobstore attributes are not always ready at this time.
230 230 for name in ['lfslocalblobstore', 'lfsremoteblobstore']:
231 231 if util.safehasattr(othervfs, name):
232 232 setattr(self, name, getattr(othervfs, name))
233 233
234 234 def _prefetchfiles(repo, revs, match):
235 235 """Ensure that required LFS blobs are present, fetching them as a group if
236 236 needed."""
237 237 if not util.safehasattr(repo.svfs, 'lfslocalblobstore'):
238 238 return
239 239
240 240 pointers = []
241 241 oids = set()
242 242 localstore = repo.svfs.lfslocalblobstore
243 243
244 244 for rev in revs:
245 245 ctx = repo[rev]
246 246 for f in ctx.walk(match):
247 247 p = pointerfromctx(ctx, f)
248 248 if p and p.oid() not in oids and not localstore.has(p.oid()):
249 249 p.filename = f
250 250 pointers.append(p)
251 251 oids.add(p.oid())
252 252
253 253 if pointers:
254 254 # Recalculating the repo store here allows 'paths.default' that is set
255 255 # on the repo by a clone command to be used for the update.
256 256 blobstore.remote(repo).readbatch(pointers, localstore)
257 257
258 258 def _canskipupload(repo):
259 259 # Skip if this hasn't been passed to reposetup()
260 260 if not util.safehasattr(repo.svfs, 'lfsremoteblobstore'):
261 261 return True
262 262
263 263 # if remotestore is a null store, upload is a no-op and can be skipped
264 264 return isinstance(repo.svfs.lfsremoteblobstore, blobstore._nullremote)
265 265
266 266 def candownload(repo):
267 267 # Skip if this hasn't been passed to reposetup()
268 268 if not util.safehasattr(repo.svfs, 'lfsremoteblobstore'):
269 269 return False
270 270
271 271 # if remotestore is a null store, downloads will lead to nothing
272 272 return not isinstance(repo.svfs.lfsremoteblobstore, blobstore._nullremote)
273 273
274 274 def uploadblobsfromrevs(repo, revs):
275 275 '''upload lfs blobs introduced by revs
276 276
277 277 Note: also used by other extensions e. g. infinitepush. avoid renaming.
278 278 '''
279 279 if _canskipupload(repo):
280 280 return
281 281 pointers = extractpointers(repo, revs)
282 282 uploadblobs(repo, pointers)
283 283
284 284 def prepush(pushop):
285 285 """Prepush hook.
286 286
287 287 Read through the revisions to push, looking for filelog entries that can be
288 288 deserialized into metadata so that we can block the push on their upload to
289 289 the remote blobstore.
290 290 """
291 291 return uploadblobsfromrevs(pushop.repo, pushop.outgoing.missing)
292 292
293 293 def push(orig, repo, remote, *args, **kwargs):
294 294 """bail on push if the extension isn't enabled on remote when needed, and
295 295 update the remote store based on the destination path."""
296 296 if 'lfs' in repo.requirements:
297 297 # If the remote peer is for a local repo, the requirement tests in the
298 298 # base class method enforce lfs support. Otherwise, some revisions in
299 299 # this repo use lfs, and the remote repo needs the extension loaded.
300 300 if not remote.local() and not remote.capable('lfs'):
301 301 # This is a copy of the message in exchange.push() when requirements
302 302 # are missing between local repos.
303 303 m = _("required features are not supported in the destination: %s")
304 304 raise error.Abort(m % 'lfs',
305 305 hint=_('enable the lfs extension on the server'))
306 306
307 307 # Repositories where this extension is disabled won't have the field.
308 308 # But if there's a requirement, then the extension must be loaded AND
309 309 # there may be blobs to push.
310 310 remotestore = repo.svfs.lfsremoteblobstore
311 311 try:
312 312 repo.svfs.lfsremoteblobstore = blobstore.remote(repo, remote.url())
313 313 return orig(repo, remote, *args, **kwargs)
314 314 finally:
315 315 repo.svfs.lfsremoteblobstore = remotestore
316 316 else:
317 317 return orig(repo, remote, *args, **kwargs)
318 318
319 319 def writenewbundle(orig, ui, repo, source, filename, bundletype, outgoing,
320 320 *args, **kwargs):
321 321 """upload LFS blobs added by outgoing revisions on 'hg bundle'"""
322 322 uploadblobsfromrevs(repo, outgoing.missing)
323 323 return orig(ui, repo, source, filename, bundletype, outgoing, *args,
324 324 **kwargs)
325 325
326 326 def extractpointers(repo, revs):
327 327 """return a list of lfs pointers added by given revs"""
328 328 repo.ui.debug('lfs: computing set of blobs to upload\n')
329 329 pointers = {}
330 330
331 331 makeprogress = repo.ui.makeprogress
332 332 with makeprogress(_('lfs search'), _('changesets'), len(revs)) as progress:
333 333 for r in revs:
334 334 ctx = repo[r]
335 335 for p in pointersfromctx(ctx).values():
336 336 pointers[p.oid()] = p
337 337 progress.increment()
338 338 return sorted(pointers.values(), key=lambda p: p.oid())
339 339
340 340 def pointerfromctx(ctx, f, removed=False):
341 341 """return a pointer for the named file from the given changectx, or None if
342 342 the file isn't LFS.
343 343
344 344 Optionally, the pointer for a file deleted from the context can be returned.
345 345 Since no such pointer is actually stored, and to distinguish from a non LFS
346 346 file, this pointer is represented by an empty dict.
347 347 """
348 348 _ctx = ctx
349 349 if f not in ctx:
350 350 if not removed:
351 351 return None
352 352 if f in ctx.p1():
353 353 _ctx = ctx.p1()
354 354 elif f in ctx.p2():
355 355 _ctx = ctx.p2()
356 356 else:
357 357 return None
358 358 fctx = _ctx[f]
359 359 if not _islfs(fctx.filelog(), fctx.filenode()):
360 360 return None
361 361 try:
362 362 p = pointer.deserialize(fctx.rawdata())
363 363 if ctx == _ctx:
364 364 return p
365 365 return {}
366 366 except pointer.InvalidPointer as ex:
367 367 raise error.Abort(_('lfs: corrupted pointer (%s@%s): %s\n')
368 368 % (f, short(_ctx.node()), ex))
369 369
370 370 def pointersfromctx(ctx, removed=False):
371 371 """return a dict {path: pointer} for given single changectx.
372 372
373 373 If ``removed`` == True and the LFS file was removed from ``ctx``, the value
374 374 stored for the path is an empty dict.
375 375 """
376 376 result = {}
377 m = ctx.repo().narrowmatch()
378
379 # TODO: consider manifest.fastread() instead
377 380 for f in ctx.files():
381 if not m(f):
382 continue
378 383 p = pointerfromctx(ctx, f, removed=removed)
379 384 if p is not None:
380 385 result[f] = p
381 386 return result
382 387
383 388 def uploadblobs(repo, pointers):
384 389 """upload given pointers from local blobstore"""
385 390 if not pointers:
386 391 return
387 392
388 393 remoteblob = repo.svfs.lfsremoteblobstore
389 394 remoteblob.writebatch(pointers, repo.svfs.lfslocalblobstore)
390 395
391 396 def upgradefinishdatamigration(orig, ui, srcrepo, dstrepo, requirements):
392 397 orig(ui, srcrepo, dstrepo, requirements)
393 398
394 399 # Skip if this hasn't been passed to reposetup()
395 400 if (util.safehasattr(srcrepo.svfs, 'lfslocalblobstore') and
396 401 util.safehasattr(dstrepo.svfs, 'lfslocalblobstore')):
397 402 srclfsvfs = srcrepo.svfs.lfslocalblobstore.vfs
398 403 dstlfsvfs = dstrepo.svfs.lfslocalblobstore.vfs
399 404
400 405 for dirpath, dirs, files in srclfsvfs.walk():
401 406 for oid in files:
402 407 ui.write(_('copying lfs blob %s\n') % oid)
403 408 lfutil.link(srclfsvfs.join(oid), dstlfsvfs.join(oid))
404 409
405 410 def upgraderequirements(orig, repo):
406 411 reqs = orig(repo)
407 412 if 'lfs' in repo.requirements:
408 413 reqs.add('lfs')
409 414 return reqs
@@ -1,166 +1,174
1 1 #testcases flat tree
2 #testcases lfs-on lfs-off
3
4 #if lfs-on
5 $ cat >> $HGRCPATH <<EOF
6 > [extensions]
7 > lfs =
8 > EOF
9 #endif
2 10
3 11 $ . "$TESTDIR/narrow-library.sh"
4 12
5 13 #if tree
6 14 $ cat << EOF >> $HGRCPATH
7 15 > [experimental]
8 16 > treemanifest = 1
9 17 > EOF
10 18 #endif
11 19
12 20 create full repo
13 21
14 22 $ hg init master
15 23 $ cd master
16 24 $ cat >> .hg/hgrc <<EOF
17 25 > [narrow]
18 26 > serveellipses=True
19 27 > EOF
20 28
21 29 $ mkdir inside
22 30 $ echo inside > inside/f1
23 31 $ mkdir outside
24 32 $ echo outside > outside/f1
25 33 $ hg ci -Aqm 'initial'
26 34
27 35 $ echo modified > inside/f1
28 36 $ hg ci -qm 'modify inside'
29 37
30 38 $ hg co -q 0
31 39 $ echo modified > outside/f1
32 40 $ hg ci -qm 'modify outside'
33 41
34 42 $ echo modified again >> outside/f1
35 43 $ hg ci -qm 'modify outside again'
36 44
37 45 $ cd ..
38 46
39 47 $ hg clone --narrow ssh://user@dummy/master narrow --include inside
40 48 requesting all changes
41 49 adding changesets
42 50 adding manifests
43 51 adding file changes
44 52 added 3 changesets with 2 changes to 1 files (+1 heads)
45 53 new changesets *:* (glob)
46 54 updating to branch default
47 55 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
48 56 $ cd narrow
49 57 $ cat >> $HGRCPATH <<EOF
50 58 > [extensions]
51 59 > strip=
52 60 > EOF
53 61
54 62 Can strip and recover changesets affecting only files within narrow spec
55 63
56 64 $ hg co -r 'desc("modify inside")'
57 65 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 66 $ rm -f .hg/strip-backup/*-backup.hg
59 67 $ hg strip .
60 68 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
61 69 saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-backup.hg (glob)
62 70 $ hg unbundle .hg/strip-backup/*-backup.hg
63 71 adding changesets
64 72 adding manifests
65 73 adding file changes
66 74 added 1 changesets with 1 changes to 1 files (+1 heads)
67 75 new changesets * (glob)
68 76 (run 'hg heads' to see heads, 'hg merge' to merge)
69 77
70 78 Can strip and recover changesets affecting files outside of narrow spec
71 79
72 80 $ hg co -r 'desc("modify outside")'
73 81 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
74 82 $ hg log -G -T '{rev} {desc}\n'
75 83 o 2 modify inside
76 84 |
77 85 | @ 1 modify outside again
78 86 |/
79 87 o 0 initial
80 88
81 89 $ hg debugdata -m 1
82 90 inside/f1\x004d6a634d5ba06331a60c29ee0db8412490a54fcd (esc) (flat !)
83 91 outside/f1\x0084ba604d54dee1f13310ce3d4ac2e8a36636691a (esc) (flat !)
84 92 inside\x006a8bc41df94075d501f9740587a0c0e13c170dc5t (esc) (tree !)
85 93 outside\x00255c2627ebdd3c7dcaa6945246f9b9f02bd45a09t (esc) (tree !)
86 94
87 95 $ rm -f .hg/strip-backup/*-backup.hg
88 96 $ hg strip .
89 97 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 98 saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-backup.hg (glob)
91 99 $ hg unbundle .hg/strip-backup/*-backup.hg
92 100 adding changesets
93 101 adding manifests
94 102 adding file changes
95 103 added 1 changesets with 0 changes to 0 files (+1 heads)
96 104 new changesets * (glob)
97 105 (run 'hg heads' to see heads, 'hg merge' to merge)
98 106 $ hg log -G -T '{rev} {desc}\n'
99 107 o 2 modify outside again
100 108 |
101 109 | o 1 modify inside
102 110 |/
103 111 @ 0 initial
104 112
105 113 Check that hash of file outside narrow spec got restored
106 114 $ hg debugdata -m 2
107 115 inside/f1\x004d6a634d5ba06331a60c29ee0db8412490a54fcd (esc) (flat !)
108 116 outside/f1\x0084ba604d54dee1f13310ce3d4ac2e8a36636691a (esc) (flat !)
109 117 inside\x006a8bc41df94075d501f9740587a0c0e13c170dc5t (esc) (tree !)
110 118 outside\x00255c2627ebdd3c7dcaa6945246f9b9f02bd45a09t (esc) (tree !)
111 119
112 120 Also verify we can apply the bundle with 'hg pull':
113 121 $ hg co -r 'desc("modify inside")'
114 122 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
115 123 $ rm .hg/strip-backup/*-backup.hg
116 124 $ hg strip .
117 125 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 126 saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-backup.hg (glob)
119 127 #if repobundlerepo
120 128 $ hg pull .hg/strip-backup/*-backup.hg
121 129 pulling from .hg/strip-backup/*-backup.hg (glob)
122 130 searching for changes
123 131 adding changesets
124 132 adding manifests
125 133 adding file changes
126 134 added 1 changesets with 1 changes to 1 files (+1 heads)
127 135 new changesets * (glob)
128 136 (run 'hg heads' to see heads, 'hg merge' to merge)
129 137
130 138 $ rm .hg/strip-backup/*-backup.hg
131 139 $ hg strip 0
132 140 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
133 141 saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-backup.hg (glob)
134 142
135 143 $ hg incoming .hg/strip-backup/*-backup.hg
136 144 comparing with .hg/strip-backup/*-backup.hg (glob)
137 145 changeset: 0:* (glob)
138 146 user: test
139 147 date: Thu Jan 01 00:00:00 1970 +0000
140 148 summary: initial
141 149
142 150 changeset: 1:9e48d953700d (flat !)
143 151 changeset: 1:3888164bccf0 (tree !)
144 152 user: test
145 153 date: Thu Jan 01 00:00:00 1970 +0000
146 154 summary: modify outside again
147 155
148 156 changeset: 2:f505d5e96aa8 (flat !)
149 157 changeset: 2:40b66f95a209 (tree !)
150 158 tag: tip
151 159 parent: 0:a99f4d53924d (flat !)
152 160 parent: 0:c2a5fabcca3c (tree !)
153 161 user: test
154 162 date: Thu Jan 01 00:00:00 1970 +0000
155 163 summary: modify inside
156 164
157 165 $ hg pull .hg/strip-backup/*-backup.hg
158 166 pulling from .hg/strip-backup/*-backup.hg (glob)
159 167 requesting all changes
160 168 adding changesets
161 169 adding manifests
162 170 adding file changes
163 171 added 3 changesets with 2 changes to 1 files (+1 heads)
164 172 new changesets *:* (glob)
165 173 (run 'hg heads' to see heads, 'hg merge' to merge)
166 174 #endif
@@ -1,399 +1,407
1 1 #testcases flat tree
2 #testcases lfs-on lfs-off
3
4 #if lfs-on
5 $ cat >> $HGRCPATH <<EOF
6 > [extensions]
7 > lfs =
8 > EOF
9 #endif
2 10
3 11 $ . "$TESTDIR/narrow-library.sh"
4 12
5 13 #if tree
6 14 $ cat << EOF >> $HGRCPATH
7 15 > [experimental]
8 16 > treemanifest = 1
9 17 > EOF
10 18 #endif
11 19
12 20 $ hg init master
13 21 $ cd master
14 22 $ cat >> .hg/hgrc <<EOF
15 23 > [narrow]
16 24 > serveellipses=True
17 25 > EOF
18 26 $ for x in `$TESTDIR/seq.py 0 10`
19 27 > do
20 28 > mkdir d$x
21 29 > echo $x > d$x/f
22 30 > hg add d$x/f
23 31 > hg commit -m "add d$x/f"
24 32 > done
25 33 $ hg log -T "{rev}: {desc}\n"
26 34 10: add d10/f
27 35 9: add d9/f
28 36 8: add d8/f
29 37 7: add d7/f
30 38 6: add d6/f
31 39 5: add d5/f
32 40 4: add d4/f
33 41 3: add d3/f
34 42 2: add d2/f
35 43 1: add d1/f
36 44 0: add d0/f
37 45 $ cd ..
38 46
39 47 Error if '.' or '..' are in the directory to track.
40 48 $ hg clone --narrow ssh://user@dummy/master foo --include ./asdf
41 49 abort: "." and ".." are not allowed in narrowspec paths
42 50 [255]
43 51 $ hg clone --narrow ssh://user@dummy/master foo --include asdf/..
44 52 abort: "." and ".." are not allowed in narrowspec paths
45 53 [255]
46 54 $ hg clone --narrow ssh://user@dummy/master foo --include a/./c
47 55 abort: "." and ".." are not allowed in narrowspec paths
48 56 [255]
49 57
50 58 Names with '.' in them are OK.
51 59 $ hg clone --narrow ssh://user@dummy/master should-work --include a/.b/c
52 60 requesting all changes
53 61 adding changesets
54 62 adding manifests
55 63 adding file changes
56 64 added 1 changesets with 0 changes to 0 files
57 65 new changesets * (glob)
58 66 updating to branch default
59 67 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 68
61 69 Test repo with local changes
62 70 $ hg clone --narrow ssh://user@dummy/master narrow-local-changes --include d0 --include d3 --include d6
63 71 requesting all changes
64 72 adding changesets
65 73 adding manifests
66 74 adding file changes
67 75 added 6 changesets with 3 changes to 3 files
68 76 new changesets *:* (glob)
69 77 updating to branch default
70 78 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
71 79 $ cd narrow-local-changes
72 80 $ cat >> $HGRCPATH << EOF
73 81 > [experimental]
74 82 > evolution=createmarkers
75 83 > EOF
76 84 $ echo local change >> d0/f
77 85 $ hg ci -m 'local change to d0'
78 86 $ hg co '.^'
79 87 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 88 $ echo local change >> d3/f
81 89 $ hg ci -m 'local hidden change to d3'
82 90 created new head
83 91 $ hg ci --amend -m 'local change to d3'
84 92 $ hg tracked --removeinclude d0
85 93 comparing with ssh://user@dummy/master
86 94 searching for changes
87 95 looking for local changes to affected paths
88 96 The following changeset(s) or their ancestors have local changes not on the remote:
89 97 * (glob)
90 98 abort: local changes found
91 99 (use --force-delete-local-changes to ignore)
92 100 [255]
93 101 Check that nothing was removed by the failed attempts
94 102 $ hg tracked
95 103 I path:d0
96 104 I path:d3
97 105 I path:d6
98 106 $ hg files
99 107 d0/f
100 108 d3/f
101 109 d6/f
102 110 $ find *
103 111 d0
104 112 d0/f
105 113 d3
106 114 d3/f
107 115 d6
108 116 d6/f
109 117 $ hg verify -q
110 118 Force deletion of local changes
111 119 $ hg log -T "{rev}: {desc} {outsidenarrow}\n"
112 120 8: local change to d3
113 121 6: local change to d0
114 122 5: add d10/f outsidenarrow
115 123 4: add d6/f
116 124 3: add d5/f outsidenarrow
117 125 2: add d3/f
118 126 1: add d2/f outsidenarrow
119 127 0: add d0/f
120 128 $ hg tracked --removeinclude d0 --force-delete-local-changes
121 129 comparing with ssh://user@dummy/master
122 130 searching for changes
123 131 looking for local changes to affected paths
124 132 The following changeset(s) or their ancestors have local changes not on the remote:
125 133 * (glob)
126 134 saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob)
127 135 deleting data/d0/f.i (reporevlogstore !)
128 136 deleting meta/d0/00manifest.i (tree !)
129 137 deleting data/d0/f/362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (reposimplestore !)
130 138 deleting data/d0/f/4374b5650fc5ae54ac857c0f0381971fdde376f7 (reposimplestore !)
131 139 deleting data/d0/f/index (reposimplestore !)
132 140
133 141 $ hg log -T "{rev}: {desc} {outsidenarrow}\n"
134 142 7: local change to d3
135 143 5: add d10/f outsidenarrow
136 144 4: add d6/f
137 145 3: add d5/f outsidenarrow
138 146 2: add d3/f
139 147 1: add d2/f outsidenarrow
140 148 0: add d0/f outsidenarrow
141 149 Can restore stripped local changes after widening
142 150 $ hg tracked --addinclude d0 -q
143 151 $ hg unbundle .hg/strip-backup/*-narrow.hg -q
144 152 $ hg --hidden co -r 'desc("local change to d0")' -q
145 153 $ cat d0/f
146 154 0
147 155 local change
148 156 Pruned commits affecting removed paths should not prevent narrowing
149 157 $ hg co '.^'
150 158 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
151 159 $ hg debugobsolete `hg log -T '{node}' -r 'desc("local change to d0")'`
152 160 obsoleted 1 changesets
153 161 $ hg tracked --removeinclude d0
154 162 comparing with ssh://user@dummy/master
155 163 searching for changes
156 164 looking for local changes to affected paths
157 165 saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob)
158 166 deleting data/d0/f.i (reporevlogstore !)
159 167 deleting meta/d0/00manifest.i (tree !)
160 168 deleting data/d0/f/362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (reposimplestore !)
161 169 deleting data/d0/f/4374b5650fc5ae54ac857c0f0381971fdde376f7 (reposimplestore !)
162 170 deleting data/d0/f/index (reposimplestore !)
163 171
164 172 Updates off of stripped commit if necessary
165 173 $ hg co -r 'desc("local change to d3")' -q
166 174 $ echo local change >> d6/f
167 175 $ hg ci -m 'local change to d6'
168 176 $ hg tracked --removeinclude d3 --force-delete-local-changes
169 177 comparing with ssh://user@dummy/master
170 178 searching for changes
171 179 looking for local changes to affected paths
172 180 The following changeset(s) or their ancestors have local changes not on the remote:
173 181 * (glob)
174 182 * (glob)
175 183 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
176 184 saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob)
177 185 deleting data/d3/f.i (reporevlogstore !)
178 186 deleting meta/d3/00manifest.i (tree !)
179 187 deleting data/d3/f/2661d26c649684b482d10f91960cc3db683c38b4 (reposimplestore !)
180 188 deleting data/d3/f/99fa7136105a15e2045ce3d9152e4837c5349e4d (reposimplestore !)
181 189 deleting data/d3/f/index (reposimplestore !)
182 190 $ hg log -T '{desc}\n' -r .
183 191 add d10/f
184 192 Updates to nullid if necessary
185 193 $ hg tracked --addinclude d3 -q
186 194 $ hg co null -q
187 195 $ mkdir d3
188 196 $ echo local change > d3/f
189 197 $ hg add d3/f
190 198 $ hg ci -m 'local change to d3'
191 199 created new head
192 200 $ hg tracked --removeinclude d3 --force-delete-local-changes
193 201 comparing with ssh://user@dummy/master
194 202 searching for changes
195 203 looking for local changes to affected paths
196 204 The following changeset(s) or their ancestors have local changes not on the remote:
197 205 * (glob)
198 206 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
199 207 saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob)
200 208 deleting data/d3/f.i (reporevlogstore !)
201 209 deleting meta/d3/00manifest.i (tree !)
202 210 deleting data/d3/f/2661d26c649684b482d10f91960cc3db683c38b4 (reposimplestore !)
203 211 deleting data/d3/f/5ce0767945cbdbca3b924bb9fbf5143f72ab40ac (reposimplestore !)
204 212 deleting data/d3/f/index (reposimplestore !)
205 213 $ hg id
206 214 000000000000
207 215 $ cd ..
208 216
209 217 Can remove last include, making repo empty
210 218 $ hg clone --narrow ssh://user@dummy/master narrow-empty --include d0 -r 5
211 219 adding changesets
212 220 adding manifests
213 221 adding file changes
214 222 added 2 changesets with 1 changes to 1 files
215 223 new changesets *:* (glob)
216 224 updating to branch default
217 225 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
218 226 $ cd narrow-empty
219 227 $ hg tracked --removeinclude d0
220 228 comparing with ssh://user@dummy/master
221 229 searching for changes
222 230 looking for local changes to affected paths
223 231 deleting data/d0/f.i (reporevlogstore !)
224 232 deleting meta/d0/00manifest.i (tree !)
225 233 deleting data/d0/f/362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (reposimplestore !)
226 234 deleting data/d0/f/index (reposimplestore !)
227 235 $ hg tracked
228 236 $ hg files
229 237 [1]
230 238 $ test -d d0
231 239 [1]
232 240 Do some work in the empty clone
233 241 $ hg diff --change .
234 242 $ hg branch foo
235 243 marked working directory as branch foo
236 244 (branches are permanent and global, did you want a bookmark?)
237 245 $ hg ci -m empty
238 246 $ hg pull -q
239 247 Can widen the empty clone
240 248 $ hg tracked --addinclude d0
241 249 comparing with ssh://user@dummy/master
242 250 searching for changes
243 251 no changes found
244 252 saved backup bundle to $TESTTMP/narrow-empty/.hg/strip-backup/*-widen.hg (glob)
245 253 adding changesets
246 254 adding manifests
247 255 adding file changes
248 256 added 3 changesets with 1 changes to 1 files
249 257 new changesets *:* (glob)
250 258 $ hg tracked
251 259 I path:d0
252 260 $ hg files
253 261 d0/f
254 262 $ find *
255 263 d0
256 264 d0/f
257 265 $ cd ..
258 266
259 267 TODO(martinvonz): test including e.g. d3/g and then removing it once
260 268 https://bitbucket.org/Google/narrowhg/issues/6 is fixed
261 269
262 270 $ hg clone --narrow ssh://user@dummy/master narrow --include d0 --include d3 --include d6 --include d9
263 271 requesting all changes
264 272 adding changesets
265 273 adding manifests
266 274 adding file changes
267 275 added 8 changesets with 4 changes to 4 files
268 276 new changesets *:* (glob)
269 277 updating to branch default
270 278 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
271 279 $ cd narrow
272 280 $ hg tracked
273 281 I path:d0
274 282 I path:d3
275 283 I path:d6
276 284 I path:d9
277 285 $ hg tracked --removeinclude d6
278 286 comparing with ssh://user@dummy/master
279 287 searching for changes
280 288 looking for local changes to affected paths
281 289 deleting data/d6/f.i (reporevlogstore !)
282 290 deleting meta/d6/00manifest.i (tree !)
283 291 deleting data/d6/f/7339d30678f451ac8c3f38753beeb4cf2e1655c7 (reposimplestore !)
284 292 deleting data/d6/f/index (reposimplestore !)
285 293 $ hg tracked
286 294 I path:d0
287 295 I path:d3
288 296 I path:d9
289 297 #if repofncache
290 298 $ hg debugrebuildfncache
291 299 fncache already up to date
292 300 #endif
293 301 $ find *
294 302 d0
295 303 d0/f
296 304 d3
297 305 d3/f
298 306 d9
299 307 d9/f
300 308 $ hg verify -q
301 309 $ hg tracked --addexclude d3/f
302 310 comparing with ssh://user@dummy/master
303 311 searching for changes
304 312 looking for local changes to affected paths
305 313 deleting data/d3/f.i (reporevlogstore !)
306 314 deleting data/d3/f/2661d26c649684b482d10f91960cc3db683c38b4 (reposimplestore !)
307 315 deleting data/d3/f/index (reposimplestore !)
308 316 $ hg tracked
309 317 I path:d0
310 318 I path:d3
311 319 I path:d9
312 320 X path:d3/f
313 321 #if repofncache
314 322 $ hg debugrebuildfncache
315 323 fncache already up to date
316 324 #endif
317 325 $ find *
318 326 d0
319 327 d0/f
320 328 d9
321 329 d9/f
322 330 $ hg verify -q
323 331 $ hg tracked --addexclude d0
324 332 comparing with ssh://user@dummy/master
325 333 searching for changes
326 334 looking for local changes to affected paths
327 335 deleting data/d0/f.i (reporevlogstore !)
328 336 deleting meta/d0/00manifest.i (tree !)
329 337 deleting data/d0/f/362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (reposimplestore !)
330 338 deleting data/d0/f/index (reposimplestore !)
331 339 $ hg tracked
332 340 I path:d3
333 341 I path:d9
334 342 X path:d0
335 343 X path:d3/f
336 344 #if repofncache
337 345 $ hg debugrebuildfncache
338 346 fncache already up to date
339 347 #endif
340 348 $ find *
341 349 d9
342 350 d9/f
343 351
344 352 Make a 15 of changes to d9 to test the path without --verbose
345 353 (Note: using regexes instead of "* (glob)" because if the test fails, it
346 354 produces more sensible diffs)
347 355 $ hg tracked
348 356 I path:d3
349 357 I path:d9
350 358 X path:d0
351 359 X path:d3/f
352 360 $ for x in `$TESTDIR/seq.py 1 15`
353 361 > do
354 362 > echo local change >> d9/f
355 363 > hg commit -m "change $x to d9/f"
356 364 > done
357 365 $ hg tracked --removeinclude d9
358 366 comparing with ssh://user@dummy/master
359 367 searching for changes
360 368 looking for local changes to affected paths
361 369 The following changeset(s) or their ancestors have local changes not on the remote:
362 370 ^[0-9a-f]{12}$ (re)
363 371 ^[0-9a-f]{12}$ (re)
364 372 ^[0-9a-f]{12}$ (re)
365 373 ^[0-9a-f]{12}$ (re)
366 374 ^[0-9a-f]{12}$ (re)
367 375 ^[0-9a-f]{12}$ (re)
368 376 ^[0-9a-f]{12}$ (re)
369 377 ^[0-9a-f]{12}$ (re)
370 378 ^[0-9a-f]{12}$ (re)
371 379 ^[0-9a-f]{12}$ (re)
372 380 ...and 5 more, use --verbose to list all
373 381 abort: local changes found
374 382 (use --force-delete-local-changes to ignore)
375 383 [255]
376 384 Now test it *with* verbose.
377 385 $ hg tracked --removeinclude d9 --verbose
378 386 comparing with ssh://user@dummy/master
379 387 searching for changes
380 388 looking for local changes to affected paths
381 389 The following changeset(s) or their ancestors have local changes not on the remote:
382 390 ^[0-9a-f]{12}$ (re)
383 391 ^[0-9a-f]{12}$ (re)
384 392 ^[0-9a-f]{12}$ (re)
385 393 ^[0-9a-f]{12}$ (re)
386 394 ^[0-9a-f]{12}$ (re)
387 395 ^[0-9a-f]{12}$ (re)
388 396 ^[0-9a-f]{12}$ (re)
389 397 ^[0-9a-f]{12}$ (re)
390 398 ^[0-9a-f]{12}$ (re)
391 399 ^[0-9a-f]{12}$ (re)
392 400 ^[0-9a-f]{12}$ (re)
393 401 ^[0-9a-f]{12}$ (re)
394 402 ^[0-9a-f]{12}$ (re)
395 403 ^[0-9a-f]{12}$ (re)
396 404 ^[0-9a-f]{12}$ (re)
397 405 abort: local changes found
398 406 (use --force-delete-local-changes to ignore)
399 407 [255]
General Comments 0
You need to be logged in to leave comments. Login now