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