##// END OF EJS Templates
largefiles: use wrappedfunction() for match() override in overridecopy()...
Martin von Zweigbergk -
r41721:9f11759f default
parent child Browse files
Show More
@@ -1,1539 +1,1538
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import copy
12 import copy
13 import os
13 import os
14
14
15 from mercurial.i18n import _
15 from mercurial.i18n import _
16
16
17 from mercurial.hgweb import (
17 from mercurial.hgweb import (
18 webcommands,
18 webcommands,
19 )
19 )
20
20
21 from mercurial import (
21 from mercurial import (
22 archival,
22 archival,
23 cmdutil,
23 cmdutil,
24 copies as copiesmod,
24 copies as copiesmod,
25 error,
25 error,
26 exchange,
26 exchange,
27 extensions,
27 extensions,
28 exthelper,
28 exthelper,
29 filemerge,
29 filemerge,
30 hg,
30 hg,
31 logcmdutil,
31 logcmdutil,
32 match as matchmod,
32 match as matchmod,
33 merge,
33 merge,
34 pathutil,
34 pathutil,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 smartset,
37 smartset,
38 subrepo,
38 subrepo,
39 upgrade,
39 upgrade,
40 url as urlmod,
40 url as urlmod,
41 util,
41 util,
42 )
42 )
43
43
44 from . import (
44 from . import (
45 lfcommands,
45 lfcommands,
46 lfutil,
46 lfutil,
47 storefactory,
47 storefactory,
48 )
48 )
49
49
50 eh = exthelper.exthelper()
50 eh = exthelper.exthelper()
51
51
52 # -- Utility functions: commonly/repeatedly needed functionality ---------------
52 # -- Utility functions: commonly/repeatedly needed functionality ---------------
53
53
54 def composelargefilematcher(match, manifest):
54 def composelargefilematcher(match, manifest):
55 '''create a matcher that matches only the largefiles in the original
55 '''create a matcher that matches only the largefiles in the original
56 matcher'''
56 matcher'''
57 m = copy.copy(match)
57 m = copy.copy(match)
58 lfile = lambda f: lfutil.standin(f) in manifest
58 lfile = lambda f: lfutil.standin(f) in manifest
59 m._files = [lf for lf in m._files if lfile(lf)]
59 m._files = [lf for lf in m._files if lfile(lf)]
60 m._fileset = set(m._files)
60 m._fileset = set(m._files)
61 m.always = lambda: False
61 m.always = lambda: False
62 origmatchfn = m.matchfn
62 origmatchfn = m.matchfn
63 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
63 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
64 return m
64 return m
65
65
66 def composenormalfilematcher(match, manifest, exclude=None):
66 def composenormalfilematcher(match, manifest, exclude=None):
67 excluded = set()
67 excluded = set()
68 if exclude is not None:
68 if exclude is not None:
69 excluded.update(exclude)
69 excluded.update(exclude)
70
70
71 m = copy.copy(match)
71 m = copy.copy(match)
72 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
72 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
73 manifest or f in excluded)
73 manifest or f in excluded)
74 m._files = [lf for lf in m._files if notlfile(lf)]
74 m._files = [lf for lf in m._files if notlfile(lf)]
75 m._fileset = set(m._files)
75 m._fileset = set(m._files)
76 m.always = lambda: False
76 m.always = lambda: False
77 origmatchfn = m.matchfn
77 origmatchfn = m.matchfn
78 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
78 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
79 return m
79 return m
80
80
81 def installnormalfilesmatchfn(manifest):
81 def installnormalfilesmatchfn(manifest):
82 '''installmatchfn with a matchfn that ignores all largefiles'''
82 '''installmatchfn with a matchfn that ignores all largefiles'''
83 def overridematch(ctx, pats=(), opts=None, globbed=False,
83 def overridematch(ctx, pats=(), opts=None, globbed=False,
84 default='relpath', badfn=None):
84 default='relpath', badfn=None):
85 if opts is None:
85 if opts is None:
86 opts = {}
86 opts = {}
87 match = oldmatch(ctx, pats, opts, globbed, default, badfn=badfn)
87 match = oldmatch(ctx, pats, opts, globbed, default, badfn=badfn)
88 return composenormalfilematcher(match, manifest)
88 return composenormalfilematcher(match, manifest)
89 oldmatch = installmatchfn(overridematch)
89 oldmatch = installmatchfn(overridematch)
90
90
91 def installmatchfn(f):
91 def installmatchfn(f):
92 '''monkey patch the scmutil module with a custom match function.
92 '''monkey patch the scmutil module with a custom match function.
93 Warning: it is monkey patching the _module_ on runtime! Not thread safe!'''
93 Warning: it is monkey patching the _module_ on runtime! Not thread safe!'''
94 oldmatch = scmutil.match
94 oldmatch = scmutil.match
95 setattr(f, 'oldmatch', oldmatch)
95 setattr(f, 'oldmatch', oldmatch)
96 scmutil.match = f
96 scmutil.match = f
97 return oldmatch
97 return oldmatch
98
98
99 def restorematchfn():
99 def restorematchfn():
100 '''restores scmutil.match to what it was before installmatchfn
100 '''restores scmutil.match to what it was before installmatchfn
101 was called. no-op if scmutil.match is its original function.
101 was called. no-op if scmutil.match is its original function.
102
102
103 Note that n calls to installmatchfn will require n calls to
103 Note that n calls to installmatchfn will require n calls to
104 restore the original matchfn.'''
104 restore the original matchfn.'''
105 scmutil.match = getattr(scmutil.match, 'oldmatch')
105 scmutil.match = getattr(scmutil.match, 'oldmatch')
106
106
107 def addlargefiles(ui, repo, isaddremove, matcher, **opts):
107 def addlargefiles(ui, repo, isaddremove, matcher, **opts):
108 large = opts.get(r'large')
108 large = opts.get(r'large')
109 lfsize = lfutil.getminsize(
109 lfsize = lfutil.getminsize(
110 ui, lfutil.islfilesrepo(repo), opts.get(r'lfsize'))
110 ui, lfutil.islfilesrepo(repo), opts.get(r'lfsize'))
111
111
112 lfmatcher = None
112 lfmatcher = None
113 if lfutil.islfilesrepo(repo):
113 if lfutil.islfilesrepo(repo):
114 lfpats = ui.configlist(lfutil.longname, 'patterns')
114 lfpats = ui.configlist(lfutil.longname, 'patterns')
115 if lfpats:
115 if lfpats:
116 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
116 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
117
117
118 lfnames = []
118 lfnames = []
119 m = matcher
119 m = matcher
120
120
121 wctx = repo[None]
121 wctx = repo[None]
122 for f in wctx.walk(matchmod.badmatch(m, lambda x, y: None)):
122 for f in wctx.walk(matchmod.badmatch(m, lambda x, y: None)):
123 exact = m.exact(f)
123 exact = m.exact(f)
124 lfile = lfutil.standin(f) in wctx
124 lfile = lfutil.standin(f) in wctx
125 nfile = f in wctx
125 nfile = f in wctx
126 exists = lfile or nfile
126 exists = lfile or nfile
127
127
128 # addremove in core gets fancy with the name, add doesn't
128 # addremove in core gets fancy with the name, add doesn't
129 if isaddremove:
129 if isaddremove:
130 name = m.uipath(f)
130 name = m.uipath(f)
131 else:
131 else:
132 name = m.rel(f)
132 name = m.rel(f)
133
133
134 # Don't warn the user when they attempt to add a normal tracked file.
134 # Don't warn the user when they attempt to add a normal tracked file.
135 # The normal add code will do that for us.
135 # The normal add code will do that for us.
136 if exact and exists:
136 if exact and exists:
137 if lfile:
137 if lfile:
138 ui.warn(_('%s already a largefile\n') % name)
138 ui.warn(_('%s already a largefile\n') % name)
139 continue
139 continue
140
140
141 if (exact or not exists) and not lfutil.isstandin(f):
141 if (exact or not exists) and not lfutil.isstandin(f):
142 # In case the file was removed previously, but not committed
142 # In case the file was removed previously, but not committed
143 # (issue3507)
143 # (issue3507)
144 if not repo.wvfs.exists(f):
144 if not repo.wvfs.exists(f):
145 continue
145 continue
146
146
147 abovemin = (lfsize and
147 abovemin = (lfsize and
148 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
148 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
149 if large or abovemin or (lfmatcher and lfmatcher(f)):
149 if large or abovemin or (lfmatcher and lfmatcher(f)):
150 lfnames.append(f)
150 lfnames.append(f)
151 if ui.verbose or not exact:
151 if ui.verbose or not exact:
152 ui.status(_('adding %s as a largefile\n') % name)
152 ui.status(_('adding %s as a largefile\n') % name)
153
153
154 bad = []
154 bad = []
155
155
156 # Need to lock, otherwise there could be a race condition between
156 # Need to lock, otherwise there could be a race condition between
157 # when standins are created and added to the repo.
157 # when standins are created and added to the repo.
158 with repo.wlock():
158 with repo.wlock():
159 if not opts.get(r'dry_run'):
159 if not opts.get(r'dry_run'):
160 standins = []
160 standins = []
161 lfdirstate = lfutil.openlfdirstate(ui, repo)
161 lfdirstate = lfutil.openlfdirstate(ui, repo)
162 for f in lfnames:
162 for f in lfnames:
163 standinname = lfutil.standin(f)
163 standinname = lfutil.standin(f)
164 lfutil.writestandin(repo, standinname, hash='',
164 lfutil.writestandin(repo, standinname, hash='',
165 executable=lfutil.getexecutable(repo.wjoin(f)))
165 executable=lfutil.getexecutable(repo.wjoin(f)))
166 standins.append(standinname)
166 standins.append(standinname)
167 if lfdirstate[f] == 'r':
167 if lfdirstate[f] == 'r':
168 lfdirstate.normallookup(f)
168 lfdirstate.normallookup(f)
169 else:
169 else:
170 lfdirstate.add(f)
170 lfdirstate.add(f)
171 lfdirstate.write()
171 lfdirstate.write()
172 bad += [lfutil.splitstandin(f)
172 bad += [lfutil.splitstandin(f)
173 for f in repo[None].add(standins)
173 for f in repo[None].add(standins)
174 if f in m.files()]
174 if f in m.files()]
175
175
176 added = [f for f in lfnames if f not in bad]
176 added = [f for f in lfnames if f not in bad]
177 return added, bad
177 return added, bad
178
178
179 def removelargefiles(ui, repo, isaddremove, matcher, dryrun, **opts):
179 def removelargefiles(ui, repo, isaddremove, matcher, dryrun, **opts):
180 after = opts.get(r'after')
180 after = opts.get(r'after')
181 m = composelargefilematcher(matcher, repo[None].manifest())
181 m = composelargefilematcher(matcher, repo[None].manifest())
182 try:
182 try:
183 repo.lfstatus = True
183 repo.lfstatus = True
184 s = repo.status(match=m, clean=not isaddremove)
184 s = repo.status(match=m, clean=not isaddremove)
185 finally:
185 finally:
186 repo.lfstatus = False
186 repo.lfstatus = False
187 manifest = repo[None].manifest()
187 manifest = repo[None].manifest()
188 modified, added, deleted, clean = [[f for f in list
188 modified, added, deleted, clean = [[f for f in list
189 if lfutil.standin(f) in manifest]
189 if lfutil.standin(f) in manifest]
190 for list in (s.modified, s.added,
190 for list in (s.modified, s.added,
191 s.deleted, s.clean)]
191 s.deleted, s.clean)]
192
192
193 def warn(files, msg):
193 def warn(files, msg):
194 for f in files:
194 for f in files:
195 ui.warn(msg % m.rel(f))
195 ui.warn(msg % m.rel(f))
196 return int(len(files) > 0)
196 return int(len(files) > 0)
197
197
198 if after:
198 if after:
199 remove = deleted
199 remove = deleted
200 result = warn(modified + added + clean,
200 result = warn(modified + added + clean,
201 _('not removing %s: file still exists\n'))
201 _('not removing %s: file still exists\n'))
202 else:
202 else:
203 remove = deleted + clean
203 remove = deleted + clean
204 result = warn(modified, _('not removing %s: file is modified (use -f'
204 result = warn(modified, _('not removing %s: file is modified (use -f'
205 ' to force removal)\n'))
205 ' to force removal)\n'))
206 result = warn(added, _('not removing %s: file has been marked for add'
206 result = warn(added, _('not removing %s: file has been marked for add'
207 ' (use forget to undo)\n')) or result
207 ' (use forget to undo)\n')) or result
208
208
209 # Need to lock because standin files are deleted then removed from the
209 # Need to lock because standin files are deleted then removed from the
210 # repository and we could race in-between.
210 # repository and we could race in-between.
211 with repo.wlock():
211 with repo.wlock():
212 lfdirstate = lfutil.openlfdirstate(ui, repo)
212 lfdirstate = lfutil.openlfdirstate(ui, repo)
213 for f in sorted(remove):
213 for f in sorted(remove):
214 if ui.verbose or not m.exact(f):
214 if ui.verbose or not m.exact(f):
215 # addremove in core gets fancy with the name, remove doesn't
215 # addremove in core gets fancy with the name, remove doesn't
216 if isaddremove:
216 if isaddremove:
217 name = m.uipath(f)
217 name = m.uipath(f)
218 else:
218 else:
219 name = m.rel(f)
219 name = m.rel(f)
220 ui.status(_('removing %s\n') % name)
220 ui.status(_('removing %s\n') % name)
221
221
222 if not dryrun:
222 if not dryrun:
223 if not after:
223 if not after:
224 repo.wvfs.unlinkpath(f, ignoremissing=True)
224 repo.wvfs.unlinkpath(f, ignoremissing=True)
225
225
226 if dryrun:
226 if dryrun:
227 return result
227 return result
228
228
229 remove = [lfutil.standin(f) for f in remove]
229 remove = [lfutil.standin(f) for f in remove]
230 # If this is being called by addremove, let the original addremove
230 # If this is being called by addremove, let the original addremove
231 # function handle this.
231 # function handle this.
232 if not isaddremove:
232 if not isaddremove:
233 for f in remove:
233 for f in remove:
234 repo.wvfs.unlinkpath(f, ignoremissing=True)
234 repo.wvfs.unlinkpath(f, ignoremissing=True)
235 repo[None].forget(remove)
235 repo[None].forget(remove)
236
236
237 for f in remove:
237 for f in remove:
238 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
238 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
239 False)
239 False)
240
240
241 lfdirstate.write()
241 lfdirstate.write()
242
242
243 return result
243 return result
244
244
245 # For overriding mercurial.hgweb.webcommands so that largefiles will
245 # For overriding mercurial.hgweb.webcommands so that largefiles will
246 # appear at their right place in the manifests.
246 # appear at their right place in the manifests.
247 @eh.wrapfunction(webcommands, 'decodepath')
247 @eh.wrapfunction(webcommands, 'decodepath')
248 def decodepath(orig, path):
248 def decodepath(orig, path):
249 return lfutil.splitstandin(path) or path
249 return lfutil.splitstandin(path) or path
250
250
251 # -- Wrappers: modify existing commands --------------------------------
251 # -- Wrappers: modify existing commands --------------------------------
252
252
253 @eh.wrapcommand('add',
253 @eh.wrapcommand('add',
254 opts=[('', 'large', None, _('add as largefile')),
254 opts=[('', 'large', None, _('add as largefile')),
255 ('', 'normal', None, _('add as normal file')),
255 ('', 'normal', None, _('add as normal file')),
256 ('', 'lfsize', '', _('add all files above this size (in megabytes) '
256 ('', 'lfsize', '', _('add all files above this size (in megabytes) '
257 'as largefiles (default: 10)'))])
257 'as largefiles (default: 10)'))])
258 def overrideadd(orig, ui, repo, *pats, **opts):
258 def overrideadd(orig, ui, repo, *pats, **opts):
259 if opts.get(r'normal') and opts.get(r'large'):
259 if opts.get(r'normal') and opts.get(r'large'):
260 raise error.Abort(_('--normal cannot be used with --large'))
260 raise error.Abort(_('--normal cannot be used with --large'))
261 return orig(ui, repo, *pats, **opts)
261 return orig(ui, repo, *pats, **opts)
262
262
263 @eh.wrapfunction(cmdutil, 'add')
263 @eh.wrapfunction(cmdutil, 'add')
264 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts):
264 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts):
265 # The --normal flag short circuits this override
265 # The --normal flag short circuits this override
266 if opts.get(r'normal'):
266 if opts.get(r'normal'):
267 return orig(ui, repo, matcher, prefix, explicitonly, **opts)
267 return orig(ui, repo, matcher, prefix, explicitonly, **opts)
268
268
269 ladded, lbad = addlargefiles(ui, repo, False, matcher, **opts)
269 ladded, lbad = addlargefiles(ui, repo, False, matcher, **opts)
270 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
270 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
271 ladded)
271 ladded)
272 bad = orig(ui, repo, normalmatcher, prefix, explicitonly, **opts)
272 bad = orig(ui, repo, normalmatcher, prefix, explicitonly, **opts)
273
273
274 bad.extend(f for f in lbad)
274 bad.extend(f for f in lbad)
275 return bad
275 return bad
276
276
277 @eh.wrapfunction(cmdutil, 'remove')
277 @eh.wrapfunction(cmdutil, 'remove')
278 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos,
278 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos,
279 dryrun):
279 dryrun):
280 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
280 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
281 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos,
281 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos,
282 dryrun)
282 dryrun)
283 return removelargefiles(ui, repo, False, matcher, dryrun, after=after,
283 return removelargefiles(ui, repo, False, matcher, dryrun, after=after,
284 force=force) or result
284 force=force) or result
285
285
286 @eh.wrapfunction(subrepo.hgsubrepo, 'status')
286 @eh.wrapfunction(subrepo.hgsubrepo, 'status')
287 def overridestatusfn(orig, repo, rev2, **opts):
287 def overridestatusfn(orig, repo, rev2, **opts):
288 try:
288 try:
289 repo._repo.lfstatus = True
289 repo._repo.lfstatus = True
290 return orig(repo, rev2, **opts)
290 return orig(repo, rev2, **opts)
291 finally:
291 finally:
292 repo._repo.lfstatus = False
292 repo._repo.lfstatus = False
293
293
294 @eh.wrapcommand('status')
294 @eh.wrapcommand('status')
295 def overridestatus(orig, ui, repo, *pats, **opts):
295 def overridestatus(orig, ui, repo, *pats, **opts):
296 try:
296 try:
297 repo.lfstatus = True
297 repo.lfstatus = True
298 return orig(ui, repo, *pats, **opts)
298 return orig(ui, repo, *pats, **opts)
299 finally:
299 finally:
300 repo.lfstatus = False
300 repo.lfstatus = False
301
301
302 @eh.wrapfunction(subrepo.hgsubrepo, 'dirty')
302 @eh.wrapfunction(subrepo.hgsubrepo, 'dirty')
303 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
303 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
304 try:
304 try:
305 repo._repo.lfstatus = True
305 repo._repo.lfstatus = True
306 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
306 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
307 finally:
307 finally:
308 repo._repo.lfstatus = False
308 repo._repo.lfstatus = False
309
309
310 @eh.wrapcommand('log')
310 @eh.wrapcommand('log')
311 def overridelog(orig, ui, repo, *pats, **opts):
311 def overridelog(orig, ui, repo, *pats, **opts):
312 def overridematchandpats(orig, ctx, pats=(), opts=None, globbed=False,
312 def overridematchandpats(orig, ctx, pats=(), opts=None, globbed=False,
313 default='relpath', badfn=None):
313 default='relpath', badfn=None):
314 """Matcher that merges root directory with .hglf, suitable for log.
314 """Matcher that merges root directory with .hglf, suitable for log.
315 It is still possible to match .hglf directly.
315 It is still possible to match .hglf directly.
316 For any listed files run log on the standin too.
316 For any listed files run log on the standin too.
317 matchfn tries both the given filename and with .hglf stripped.
317 matchfn tries both the given filename and with .hglf stripped.
318 """
318 """
319 if opts is None:
319 if opts is None:
320 opts = {}
320 opts = {}
321 matchandpats = orig(ctx, pats, opts, globbed, default, badfn=badfn)
321 matchandpats = orig(ctx, pats, opts, globbed, default, badfn=badfn)
322 m, p = copy.copy(matchandpats)
322 m, p = copy.copy(matchandpats)
323
323
324 if m.always():
324 if m.always():
325 # We want to match everything anyway, so there's no benefit trying
325 # We want to match everything anyway, so there's no benefit trying
326 # to add standins.
326 # to add standins.
327 return matchandpats
327 return matchandpats
328
328
329 pats = set(p)
329 pats = set(p)
330
330
331 def fixpats(pat, tostandin=lfutil.standin):
331 def fixpats(pat, tostandin=lfutil.standin):
332 if pat.startswith('set:'):
332 if pat.startswith('set:'):
333 return pat
333 return pat
334
334
335 kindpat = matchmod._patsplit(pat, None)
335 kindpat = matchmod._patsplit(pat, None)
336
336
337 if kindpat[0] is not None:
337 if kindpat[0] is not None:
338 return kindpat[0] + ':' + tostandin(kindpat[1])
338 return kindpat[0] + ':' + tostandin(kindpat[1])
339 return tostandin(kindpat[1])
339 return tostandin(kindpat[1])
340
340
341 if m._cwd:
341 if m._cwd:
342 hglf = lfutil.shortname
342 hglf = lfutil.shortname
343 back = util.pconvert(m.rel(hglf)[:-len(hglf)])
343 back = util.pconvert(m.rel(hglf)[:-len(hglf)])
344
344
345 def tostandin(f):
345 def tostandin(f):
346 # The file may already be a standin, so truncate the back
346 # The file may already be a standin, so truncate the back
347 # prefix and test before mangling it. This avoids turning
347 # prefix and test before mangling it. This avoids turning
348 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
348 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
349 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
349 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
350 return f
350 return f
351
351
352 # An absolute path is from outside the repo, so truncate the
352 # An absolute path is from outside the repo, so truncate the
353 # path to the root before building the standin. Otherwise cwd
353 # path to the root before building the standin. Otherwise cwd
354 # is somewhere in the repo, relative to root, and needs to be
354 # is somewhere in the repo, relative to root, and needs to be
355 # prepended before building the standin.
355 # prepended before building the standin.
356 if os.path.isabs(m._cwd):
356 if os.path.isabs(m._cwd):
357 f = f[len(back):]
357 f = f[len(back):]
358 else:
358 else:
359 f = m._cwd + '/' + f
359 f = m._cwd + '/' + f
360 return back + lfutil.standin(f)
360 return back + lfutil.standin(f)
361 else:
361 else:
362 def tostandin(f):
362 def tostandin(f):
363 if lfutil.isstandin(f):
363 if lfutil.isstandin(f):
364 return f
364 return f
365 return lfutil.standin(f)
365 return lfutil.standin(f)
366 pats.update(fixpats(f, tostandin) for f in p)
366 pats.update(fixpats(f, tostandin) for f in p)
367
367
368 for i in range(0, len(m._files)):
368 for i in range(0, len(m._files)):
369 # Don't add '.hglf' to m.files, since that is already covered by '.'
369 # Don't add '.hglf' to m.files, since that is already covered by '.'
370 if m._files[i] == '.':
370 if m._files[i] == '.':
371 continue
371 continue
372 standin = lfutil.standin(m._files[i])
372 standin = lfutil.standin(m._files[i])
373 # If the "standin" is a directory, append instead of replace to
373 # If the "standin" is a directory, append instead of replace to
374 # support naming a directory on the command line with only
374 # support naming a directory on the command line with only
375 # largefiles. The original directory is kept to support normal
375 # largefiles. The original directory is kept to support normal
376 # files.
376 # files.
377 if standin in ctx:
377 if standin in ctx:
378 m._files[i] = standin
378 m._files[i] = standin
379 elif m._files[i] not in ctx and repo.wvfs.isdir(standin):
379 elif m._files[i] not in ctx and repo.wvfs.isdir(standin):
380 m._files.append(standin)
380 m._files.append(standin)
381
381
382 m._fileset = set(m._files)
382 m._fileset = set(m._files)
383 m.always = lambda: False
383 m.always = lambda: False
384 origmatchfn = m.matchfn
384 origmatchfn = m.matchfn
385 def lfmatchfn(f):
385 def lfmatchfn(f):
386 lf = lfutil.splitstandin(f)
386 lf = lfutil.splitstandin(f)
387 if lf is not None and origmatchfn(lf):
387 if lf is not None and origmatchfn(lf):
388 return True
388 return True
389 r = origmatchfn(f)
389 r = origmatchfn(f)
390 return r
390 return r
391 m.matchfn = lfmatchfn
391 m.matchfn = lfmatchfn
392
392
393 ui.debug('updated patterns: %s\n' % ', '.join(sorted(pats)))
393 ui.debug('updated patterns: %s\n' % ', '.join(sorted(pats)))
394 return m, pats
394 return m, pats
395
395
396 # For hg log --patch, the match object is used in two different senses:
396 # For hg log --patch, the match object is used in two different senses:
397 # (1) to determine what revisions should be printed out, and
397 # (1) to determine what revisions should be printed out, and
398 # (2) to determine what files to print out diffs for.
398 # (2) to determine what files to print out diffs for.
399 # The magic matchandpats override should be used for case (1) but not for
399 # The magic matchandpats override should be used for case (1) but not for
400 # case (2).
400 # case (2).
401 oldmatchandpats = scmutil.matchandpats
401 oldmatchandpats = scmutil.matchandpats
402 def overridemakefilematcher(orig, repo, pats, opts, badfn=None):
402 def overridemakefilematcher(orig, repo, pats, opts, badfn=None):
403 wctx = repo[None]
403 wctx = repo[None]
404 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
404 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
405 return lambda ctx: match
405 return lambda ctx: match
406
406
407 wrappedmatchandpats = extensions.wrappedfunction(scmutil, 'matchandpats',
407 wrappedmatchandpats = extensions.wrappedfunction(scmutil, 'matchandpats',
408 overridematchandpats)
408 overridematchandpats)
409 wrappedmakefilematcher = extensions.wrappedfunction(
409 wrappedmakefilematcher = extensions.wrappedfunction(
410 logcmdutil, '_makenofollowfilematcher', overridemakefilematcher)
410 logcmdutil, '_makenofollowfilematcher', overridemakefilematcher)
411 with wrappedmatchandpats, wrappedmakefilematcher:
411 with wrappedmatchandpats, wrappedmakefilematcher:
412 return orig(ui, repo, *pats, **opts)
412 return orig(ui, repo, *pats, **opts)
413
413
414 @eh.wrapcommand('verify',
414 @eh.wrapcommand('verify',
415 opts=[('', 'large', None,
415 opts=[('', 'large', None,
416 _('verify that all largefiles in current revision exists')),
416 _('verify that all largefiles in current revision exists')),
417 ('', 'lfa', None,
417 ('', 'lfa', None,
418 _('verify largefiles in all revisions, not just current')),
418 _('verify largefiles in all revisions, not just current')),
419 ('', 'lfc', None,
419 ('', 'lfc', None,
420 _('verify local largefile contents, not just existence'))])
420 _('verify local largefile contents, not just existence'))])
421 def overrideverify(orig, ui, repo, *pats, **opts):
421 def overrideverify(orig, ui, repo, *pats, **opts):
422 large = opts.pop(r'large', False)
422 large = opts.pop(r'large', False)
423 all = opts.pop(r'lfa', False)
423 all = opts.pop(r'lfa', False)
424 contents = opts.pop(r'lfc', False)
424 contents = opts.pop(r'lfc', False)
425
425
426 result = orig(ui, repo, *pats, **opts)
426 result = orig(ui, repo, *pats, **opts)
427 if large or all or contents:
427 if large or all or contents:
428 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
428 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
429 return result
429 return result
430
430
431 @eh.wrapcommand('debugstate',
431 @eh.wrapcommand('debugstate',
432 opts=[('', 'large', None, _('display largefiles dirstate'))])
432 opts=[('', 'large', None, _('display largefiles dirstate'))])
433 def overridedebugstate(orig, ui, repo, *pats, **opts):
433 def overridedebugstate(orig, ui, repo, *pats, **opts):
434 large = opts.pop(r'large', False)
434 large = opts.pop(r'large', False)
435 if large:
435 if large:
436 class fakerepo(object):
436 class fakerepo(object):
437 dirstate = lfutil.openlfdirstate(ui, repo)
437 dirstate = lfutil.openlfdirstate(ui, repo)
438 orig(ui, fakerepo, *pats, **opts)
438 orig(ui, fakerepo, *pats, **opts)
439 else:
439 else:
440 orig(ui, repo, *pats, **opts)
440 orig(ui, repo, *pats, **opts)
441
441
442 # Before starting the manifest merge, merge.updates will call
442 # Before starting the manifest merge, merge.updates will call
443 # _checkunknownfile to check if there are any files in the merged-in
443 # _checkunknownfile to check if there are any files in the merged-in
444 # changeset that collide with unknown files in the working copy.
444 # changeset that collide with unknown files in the working copy.
445 #
445 #
446 # The largefiles are seen as unknown, so this prevents us from merging
446 # The largefiles are seen as unknown, so this prevents us from merging
447 # in a file 'foo' if we already have a largefile with the same name.
447 # in a file 'foo' if we already have a largefile with the same name.
448 #
448 #
449 # The overridden function filters the unknown files by removing any
449 # The overridden function filters the unknown files by removing any
450 # largefiles. This makes the merge proceed and we can then handle this
450 # largefiles. This makes the merge proceed and we can then handle this
451 # case further in the overridden calculateupdates function below.
451 # case further in the overridden calculateupdates function below.
452 @eh.wrapfunction(merge, '_checkunknownfile')
452 @eh.wrapfunction(merge, '_checkunknownfile')
453 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
453 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
454 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
454 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
455 return False
455 return False
456 return origfn(repo, wctx, mctx, f, f2)
456 return origfn(repo, wctx, mctx, f, f2)
457
457
458 # The manifest merge handles conflicts on the manifest level. We want
458 # The manifest merge handles conflicts on the manifest level. We want
459 # to handle changes in largefile-ness of files at this level too.
459 # to handle changes in largefile-ness of files at this level too.
460 #
460 #
461 # The strategy is to run the original calculateupdates and then process
461 # The strategy is to run the original calculateupdates and then process
462 # the action list it outputs. There are two cases we need to deal with:
462 # the action list it outputs. There are two cases we need to deal with:
463 #
463 #
464 # 1. Normal file in p1, largefile in p2. Here the largefile is
464 # 1. Normal file in p1, largefile in p2. Here the largefile is
465 # detected via its standin file, which will enter the working copy
465 # detected via its standin file, which will enter the working copy
466 # with a "get" action. It is not "merge" since the standin is all
466 # with a "get" action. It is not "merge" since the standin is all
467 # Mercurial is concerned with at this level -- the link to the
467 # Mercurial is concerned with at this level -- the link to the
468 # existing normal file is not relevant here.
468 # existing normal file is not relevant here.
469 #
469 #
470 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
470 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
471 # since the largefile will be present in the working copy and
471 # since the largefile will be present in the working copy and
472 # different from the normal file in p2. Mercurial therefore
472 # different from the normal file in p2. Mercurial therefore
473 # triggers a merge action.
473 # triggers a merge action.
474 #
474 #
475 # In both cases, we prompt the user and emit new actions to either
475 # In both cases, we prompt the user and emit new actions to either
476 # remove the standin (if the normal file was kept) or to remove the
476 # remove the standin (if the normal file was kept) or to remove the
477 # normal file and get the standin (if the largefile was kept). The
477 # normal file and get the standin (if the largefile was kept). The
478 # default prompt answer is to use the largefile version since it was
478 # default prompt answer is to use the largefile version since it was
479 # presumably changed on purpose.
479 # presumably changed on purpose.
480 #
480 #
481 # Finally, the merge.applyupdates function will then take care of
481 # Finally, the merge.applyupdates function will then take care of
482 # writing the files into the working copy and lfcommands.updatelfiles
482 # writing the files into the working copy and lfcommands.updatelfiles
483 # will update the largefiles.
483 # will update the largefiles.
484 @eh.wrapfunction(merge, 'calculateupdates')
484 @eh.wrapfunction(merge, 'calculateupdates')
485 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
485 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
486 acceptremote, *args, **kwargs):
486 acceptremote, *args, **kwargs):
487 overwrite = force and not branchmerge
487 overwrite = force and not branchmerge
488 actions, diverge, renamedelete = origfn(
488 actions, diverge, renamedelete = origfn(
489 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
489 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
490
490
491 if overwrite:
491 if overwrite:
492 return actions, diverge, renamedelete
492 return actions, diverge, renamedelete
493
493
494 # Convert to dictionary with filename as key and action as value.
494 # Convert to dictionary with filename as key and action as value.
495 lfiles = set()
495 lfiles = set()
496 for f in actions:
496 for f in actions:
497 splitstandin = lfutil.splitstandin(f)
497 splitstandin = lfutil.splitstandin(f)
498 if splitstandin in p1:
498 if splitstandin in p1:
499 lfiles.add(splitstandin)
499 lfiles.add(splitstandin)
500 elif lfutil.standin(f) in p1:
500 elif lfutil.standin(f) in p1:
501 lfiles.add(f)
501 lfiles.add(f)
502
502
503 for lfile in sorted(lfiles):
503 for lfile in sorted(lfiles):
504 standin = lfutil.standin(lfile)
504 standin = lfutil.standin(lfile)
505 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
505 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
506 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
506 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
507 if sm in ('g', 'dc') and lm != 'r':
507 if sm in ('g', 'dc') and lm != 'r':
508 if sm == 'dc':
508 if sm == 'dc':
509 f1, f2, fa, move, anc = sargs
509 f1, f2, fa, move, anc = sargs
510 sargs = (p2[f2].flags(), False)
510 sargs = (p2[f2].flags(), False)
511 # Case 1: normal file in the working copy, largefile in
511 # Case 1: normal file in the working copy, largefile in
512 # the second parent
512 # the second parent
513 usermsg = _('remote turned local normal file %s into a largefile\n'
513 usermsg = _('remote turned local normal file %s into a largefile\n'
514 'use (l)argefile or keep (n)ormal file?'
514 'use (l)argefile or keep (n)ormal file?'
515 '$$ &Largefile $$ &Normal file') % lfile
515 '$$ &Largefile $$ &Normal file') % lfile
516 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
516 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
517 actions[lfile] = ('r', None, 'replaced by standin')
517 actions[lfile] = ('r', None, 'replaced by standin')
518 actions[standin] = ('g', sargs, 'replaces standin')
518 actions[standin] = ('g', sargs, 'replaces standin')
519 else: # keep local normal file
519 else: # keep local normal file
520 actions[lfile] = ('k', None, 'replaces standin')
520 actions[lfile] = ('k', None, 'replaces standin')
521 if branchmerge:
521 if branchmerge:
522 actions[standin] = ('k', None, 'replaced by non-standin')
522 actions[standin] = ('k', None, 'replaced by non-standin')
523 else:
523 else:
524 actions[standin] = ('r', None, 'replaced by non-standin')
524 actions[standin] = ('r', None, 'replaced by non-standin')
525 elif lm in ('g', 'dc') and sm != 'r':
525 elif lm in ('g', 'dc') and sm != 'r':
526 if lm == 'dc':
526 if lm == 'dc':
527 f1, f2, fa, move, anc = largs
527 f1, f2, fa, move, anc = largs
528 largs = (p2[f2].flags(), False)
528 largs = (p2[f2].flags(), False)
529 # Case 2: largefile in the working copy, normal file in
529 # Case 2: largefile in the working copy, normal file in
530 # the second parent
530 # the second parent
531 usermsg = _('remote turned local largefile %s into a normal file\n'
531 usermsg = _('remote turned local largefile %s into a normal file\n'
532 'keep (l)argefile or use (n)ormal file?'
532 'keep (l)argefile or use (n)ormal file?'
533 '$$ &Largefile $$ &Normal file') % lfile
533 '$$ &Largefile $$ &Normal file') % lfile
534 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
534 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
535 if branchmerge:
535 if branchmerge:
536 # largefile can be restored from standin safely
536 # largefile can be restored from standin safely
537 actions[lfile] = ('k', None, 'replaced by standin')
537 actions[lfile] = ('k', None, 'replaced by standin')
538 actions[standin] = ('k', None, 'replaces standin')
538 actions[standin] = ('k', None, 'replaces standin')
539 else:
539 else:
540 # "lfile" should be marked as "removed" without
540 # "lfile" should be marked as "removed" without
541 # removal of itself
541 # removal of itself
542 actions[lfile] = ('lfmr', None,
542 actions[lfile] = ('lfmr', None,
543 'forget non-standin largefile')
543 'forget non-standin largefile')
544
544
545 # linear-merge should treat this largefile as 're-added'
545 # linear-merge should treat this largefile as 're-added'
546 actions[standin] = ('a', None, 'keep standin')
546 actions[standin] = ('a', None, 'keep standin')
547 else: # pick remote normal file
547 else: # pick remote normal file
548 actions[lfile] = ('g', largs, 'replaces standin')
548 actions[lfile] = ('g', largs, 'replaces standin')
549 actions[standin] = ('r', None, 'replaced by non-standin')
549 actions[standin] = ('r', None, 'replaced by non-standin')
550
550
551 return actions, diverge, renamedelete
551 return actions, diverge, renamedelete
552
552
553 @eh.wrapfunction(merge, 'recordupdates')
553 @eh.wrapfunction(merge, 'recordupdates')
554 def mergerecordupdates(orig, repo, actions, branchmerge):
554 def mergerecordupdates(orig, repo, actions, branchmerge):
555 if 'lfmr' in actions:
555 if 'lfmr' in actions:
556 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
556 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
557 for lfile, args, msg in actions['lfmr']:
557 for lfile, args, msg in actions['lfmr']:
558 # this should be executed before 'orig', to execute 'remove'
558 # this should be executed before 'orig', to execute 'remove'
559 # before all other actions
559 # before all other actions
560 repo.dirstate.remove(lfile)
560 repo.dirstate.remove(lfile)
561 # make sure lfile doesn't get synclfdirstate'd as normal
561 # make sure lfile doesn't get synclfdirstate'd as normal
562 lfdirstate.add(lfile)
562 lfdirstate.add(lfile)
563 lfdirstate.write()
563 lfdirstate.write()
564
564
565 return orig(repo, actions, branchmerge)
565 return orig(repo, actions, branchmerge)
566
566
567 # Override filemerge to prompt the user about how they wish to merge
567 # Override filemerge to prompt the user about how they wish to merge
568 # largefiles. This will handle identical edits without prompting the user.
568 # largefiles. This will handle identical edits without prompting the user.
569 @eh.wrapfunction(filemerge, '_filemerge')
569 @eh.wrapfunction(filemerge, '_filemerge')
570 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
570 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
571 labels=None):
571 labels=None):
572 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
572 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
573 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
573 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
574 labels=labels)
574 labels=labels)
575
575
576 ahash = lfutil.readasstandin(fca).lower()
576 ahash = lfutil.readasstandin(fca).lower()
577 dhash = lfutil.readasstandin(fcd).lower()
577 dhash = lfutil.readasstandin(fcd).lower()
578 ohash = lfutil.readasstandin(fco).lower()
578 ohash = lfutil.readasstandin(fco).lower()
579 if (ohash != ahash and
579 if (ohash != ahash and
580 ohash != dhash and
580 ohash != dhash and
581 (dhash == ahash or
581 (dhash == ahash or
582 repo.ui.promptchoice(
582 repo.ui.promptchoice(
583 _('largefile %s has a merge conflict\nancestor was %s\n'
583 _('largefile %s has a merge conflict\nancestor was %s\n'
584 'keep (l)ocal %s or\ntake (o)ther %s?'
584 'keep (l)ocal %s or\ntake (o)ther %s?'
585 '$$ &Local $$ &Other') %
585 '$$ &Local $$ &Other') %
586 (lfutil.splitstandin(orig), ahash, dhash, ohash),
586 (lfutil.splitstandin(orig), ahash, dhash, ohash),
587 0) == 1)):
587 0) == 1)):
588 repo.wwrite(fcd.path(), fco.data(), fco.flags())
588 repo.wwrite(fcd.path(), fco.data(), fco.flags())
589 return True, 0, False
589 return True, 0, False
590
590
591 @eh.wrapfunction(copiesmod, 'pathcopies')
591 @eh.wrapfunction(copiesmod, 'pathcopies')
592 def copiespathcopies(orig, ctx1, ctx2, match=None):
592 def copiespathcopies(orig, ctx1, ctx2, match=None):
593 copies = orig(ctx1, ctx2, match=match)
593 copies = orig(ctx1, ctx2, match=match)
594 updated = {}
594 updated = {}
595
595
596 for k, v in copies.iteritems():
596 for k, v in copies.iteritems():
597 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
597 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
598
598
599 return updated
599 return updated
600
600
601 # Copy first changes the matchers to match standins instead of
601 # Copy first changes the matchers to match standins instead of
602 # largefiles. Then it overrides util.copyfile in that function it
602 # largefiles. Then it overrides util.copyfile in that function it
603 # checks if the destination largefile already exists. It also keeps a
603 # checks if the destination largefile already exists. It also keeps a
604 # list of copied files so that the largefiles can be copied and the
604 # list of copied files so that the largefiles can be copied and the
605 # dirstate updated.
605 # dirstate updated.
606 @eh.wrapfunction(cmdutil, 'copy')
606 @eh.wrapfunction(cmdutil, 'copy')
607 def overridecopy(orig, ui, repo, pats, opts, rename=False):
607 def overridecopy(orig, ui, repo, pats, opts, rename=False):
608 # doesn't remove largefile on rename
608 # doesn't remove largefile on rename
609 if len(pats) < 2:
609 if len(pats) < 2:
610 # this isn't legal, let the original function deal with it
610 # this isn't legal, let the original function deal with it
611 return orig(ui, repo, pats, opts, rename)
611 return orig(ui, repo, pats, opts, rename)
612
612
613 # This could copy both lfiles and normal files in one command,
613 # This could copy both lfiles and normal files in one command,
614 # but we don't want to do that. First replace their matcher to
614 # but we don't want to do that. First replace their matcher to
615 # only match normal files and run it, then replace it to just
615 # only match normal files and run it, then replace it to just
616 # match largefiles and run it again.
616 # match largefiles and run it again.
617 nonormalfiles = False
617 nonormalfiles = False
618 nolfiles = False
618 nolfiles = False
619 installnormalfilesmatchfn(repo[None].manifest())
619 installnormalfilesmatchfn(repo[None].manifest())
620 try:
620 try:
621 result = orig(ui, repo, pats, opts, rename)
621 result = orig(ui, repo, pats, opts, rename)
622 except error.Abort as e:
622 except error.Abort as e:
623 if pycompat.bytestr(e) != _('no files to copy'):
623 if pycompat.bytestr(e) != _('no files to copy'):
624 raise e
624 raise e
625 else:
625 else:
626 nonormalfiles = True
626 nonormalfiles = True
627 result = 0
627 result = 0
628 finally:
628 finally:
629 restorematchfn()
629 restorematchfn()
630
630
631 # The first rename can cause our current working directory to be removed.
631 # The first rename can cause our current working directory to be removed.
632 # In that case there is nothing left to copy/rename so just quit.
632 # In that case there is nothing left to copy/rename so just quit.
633 try:
633 try:
634 repo.getcwd()
634 repo.getcwd()
635 except OSError:
635 except OSError:
636 return result
636 return result
637
637
638 def makestandin(relpath):
638 def makestandin(relpath):
639 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
639 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
640 return repo.wvfs.join(lfutil.standin(path))
640 return repo.wvfs.join(lfutil.standin(path))
641
641
642 fullpats = scmutil.expandpats(pats)
642 fullpats = scmutil.expandpats(pats)
643 dest = fullpats[-1]
643 dest = fullpats[-1]
644
644
645 if os.path.isdir(dest):
645 if os.path.isdir(dest):
646 if not os.path.isdir(makestandin(dest)):
646 if not os.path.isdir(makestandin(dest)):
647 os.makedirs(makestandin(dest))
647 os.makedirs(makestandin(dest))
648
648
649 try:
649 try:
650 # When we call orig below it creates the standins but we don't add
650 # When we call orig below it creates the standins but we don't add
651 # them to the dir state until later so lock during that time.
651 # them to the dir state until later so lock during that time.
652 wlock = repo.wlock()
652 wlock = repo.wlock()
653
653
654 manifest = repo[None].manifest()
654 manifest = repo[None].manifest()
655 def overridematch(ctx, pats=(), opts=None, globbed=False,
655 def overridematch(orig, ctx, pats=(), opts=None, globbed=False,
656 default='relpath', badfn=None):
656 default='relpath', badfn=None):
657 if opts is None:
657 if opts is None:
658 opts = {}
658 opts = {}
659 newpats = []
659 newpats = []
660 # The patterns were previously mangled to add the standin
660 # The patterns were previously mangled to add the standin
661 # directory; we need to remove that now
661 # directory; we need to remove that now
662 for pat in pats:
662 for pat in pats:
663 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
663 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
664 newpats.append(pat.replace(lfutil.shortname, ''))
664 newpats.append(pat.replace(lfutil.shortname, ''))
665 else:
665 else:
666 newpats.append(pat)
666 newpats.append(pat)
667 match = oldmatch(ctx, newpats, opts, globbed, default, badfn=badfn)
667 match = orig(ctx, newpats, opts, globbed, default, badfn=badfn)
668 m = copy.copy(match)
668 m = copy.copy(match)
669 lfile = lambda f: lfutil.standin(f) in manifest
669 lfile = lambda f: lfutil.standin(f) in manifest
670 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
670 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
671 m._fileset = set(m._files)
671 m._fileset = set(m._files)
672 origmatchfn = m.matchfn
672 origmatchfn = m.matchfn
673 def matchfn(f):
673 def matchfn(f):
674 lfile = lfutil.splitstandin(f)
674 lfile = lfutil.splitstandin(f)
675 return (lfile is not None and
675 return (lfile is not None and
676 (f in manifest) and
676 (f in manifest) and
677 origmatchfn(lfile) or
677 origmatchfn(lfile) or
678 None)
678 None)
679 m.matchfn = matchfn
679 m.matchfn = matchfn
680 return m
680 return m
681 oldmatch = installmatchfn(overridematch)
682 listpats = []
681 listpats = []
683 for pat in pats:
682 for pat in pats:
684 if matchmod.patkind(pat) is not None:
683 if matchmod.patkind(pat) is not None:
685 listpats.append(pat)
684 listpats.append(pat)
686 else:
685 else:
687 listpats.append(makestandin(pat))
686 listpats.append(makestandin(pat))
688
687
689 copiedfiles = []
688 copiedfiles = []
690 def overridecopyfile(orig, src, dest, *args, **kwargs):
689 def overridecopyfile(orig, src, dest, *args, **kwargs):
691 if (lfutil.shortname in src and
690 if (lfutil.shortname in src and
692 dest.startswith(repo.wjoin(lfutil.shortname))):
691 dest.startswith(repo.wjoin(lfutil.shortname))):
693 destlfile = dest.replace(lfutil.shortname, '')
692 destlfile = dest.replace(lfutil.shortname, '')
694 if not opts['force'] and os.path.exists(destlfile):
693 if not opts['force'] and os.path.exists(destlfile):
695 raise IOError('',
694 raise IOError('',
696 _('destination largefile already exists'))
695 _('destination largefile already exists'))
697 copiedfiles.append((src, dest))
696 copiedfiles.append((src, dest))
698 orig(src, dest, *args, **kwargs)
697 orig(src, dest, *args, **kwargs)
699 with extensions.wrappedfunction(util, 'copyfile', overridecopyfile):
698 with extensions.wrappedfunction(util, 'copyfile', overridecopyfile), \
699 extensions.wrappedfunction(scmutil, 'match', overridematch):
700 result += orig(ui, repo, listpats, opts, rename)
700 result += orig(ui, repo, listpats, opts, rename)
701
701
702 lfdirstate = lfutil.openlfdirstate(ui, repo)
702 lfdirstate = lfutil.openlfdirstate(ui, repo)
703 for (src, dest) in copiedfiles:
703 for (src, dest) in copiedfiles:
704 if (lfutil.shortname in src and
704 if (lfutil.shortname in src and
705 dest.startswith(repo.wjoin(lfutil.shortname))):
705 dest.startswith(repo.wjoin(lfutil.shortname))):
706 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
706 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
707 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
707 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
708 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
708 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
709 if not os.path.isdir(destlfiledir):
709 if not os.path.isdir(destlfiledir):
710 os.makedirs(destlfiledir)
710 os.makedirs(destlfiledir)
711 if rename:
711 if rename:
712 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
712 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
713
713
714 # The file is gone, but this deletes any empty parent
714 # The file is gone, but this deletes any empty parent
715 # directories as a side-effect.
715 # directories as a side-effect.
716 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
716 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
717 lfdirstate.remove(srclfile)
717 lfdirstate.remove(srclfile)
718 else:
718 else:
719 util.copyfile(repo.wjoin(srclfile),
719 util.copyfile(repo.wjoin(srclfile),
720 repo.wjoin(destlfile))
720 repo.wjoin(destlfile))
721
721
722 lfdirstate.add(destlfile)
722 lfdirstate.add(destlfile)
723 lfdirstate.write()
723 lfdirstate.write()
724 except error.Abort as e:
724 except error.Abort as e:
725 if pycompat.bytestr(e) != _('no files to copy'):
725 if pycompat.bytestr(e) != _('no files to copy'):
726 raise e
726 raise e
727 else:
727 else:
728 nolfiles = True
728 nolfiles = True
729 finally:
729 finally:
730 restorematchfn()
731 wlock.release()
730 wlock.release()
732
731
733 if nolfiles and nonormalfiles:
732 if nolfiles and nonormalfiles:
734 raise error.Abort(_('no files to copy'))
733 raise error.Abort(_('no files to copy'))
735
734
736 return result
735 return result
737
736
738 # When the user calls revert, we have to be careful to not revert any
737 # When the user calls revert, we have to be careful to not revert any
739 # changes to other largefiles accidentally. This means we have to keep
738 # changes to other largefiles accidentally. This means we have to keep
740 # track of the largefiles that are being reverted so we only pull down
739 # track of the largefiles that are being reverted so we only pull down
741 # the necessary largefiles.
740 # the necessary largefiles.
742 #
741 #
743 # Standins are only updated (to match the hash of largefiles) before
742 # Standins are only updated (to match the hash of largefiles) before
744 # commits. Update the standins then run the original revert, changing
743 # commits. Update the standins then run the original revert, changing
745 # the matcher to hit standins instead of largefiles. Based on the
744 # the matcher to hit standins instead of largefiles. Based on the
746 # resulting standins update the largefiles.
745 # resulting standins update the largefiles.
747 @eh.wrapfunction(cmdutil, 'revert')
746 @eh.wrapfunction(cmdutil, 'revert')
748 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
747 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
749 # Because we put the standins in a bad state (by updating them)
748 # Because we put the standins in a bad state (by updating them)
750 # and then return them to a correct state we need to lock to
749 # and then return them to a correct state we need to lock to
751 # prevent others from changing them in their incorrect state.
750 # prevent others from changing them in their incorrect state.
752 with repo.wlock():
751 with repo.wlock():
753 lfdirstate = lfutil.openlfdirstate(ui, repo)
752 lfdirstate = lfutil.openlfdirstate(ui, repo)
754 s = lfutil.lfdirstatestatus(lfdirstate, repo)
753 s = lfutil.lfdirstatestatus(lfdirstate, repo)
755 lfdirstate.write()
754 lfdirstate.write()
756 for lfile in s.modified:
755 for lfile in s.modified:
757 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
756 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
758 for lfile in s.deleted:
757 for lfile in s.deleted:
759 fstandin = lfutil.standin(lfile)
758 fstandin = lfutil.standin(lfile)
760 if (repo.wvfs.exists(fstandin)):
759 if (repo.wvfs.exists(fstandin)):
761 repo.wvfs.unlink(fstandin)
760 repo.wvfs.unlink(fstandin)
762
761
763 oldstandins = lfutil.getstandinsstate(repo)
762 oldstandins = lfutil.getstandinsstate(repo)
764
763
765 def overridematch(mctx, pats=(), opts=None, globbed=False,
764 def overridematch(mctx, pats=(), opts=None, globbed=False,
766 default='relpath', badfn=None):
765 default='relpath', badfn=None):
767 if opts is None:
766 if opts is None:
768 opts = {}
767 opts = {}
769 match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn)
768 match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn)
770 m = copy.copy(match)
769 m = copy.copy(match)
771
770
772 # revert supports recursing into subrepos, and though largefiles
771 # revert supports recursing into subrepos, and though largefiles
773 # currently doesn't work correctly in that case, this match is
772 # currently doesn't work correctly in that case, this match is
774 # called, so the lfdirstate above may not be the correct one for
773 # called, so the lfdirstate above may not be the correct one for
775 # this invocation of match.
774 # this invocation of match.
776 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
775 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
777 False)
776 False)
778
777
779 wctx = repo[None]
778 wctx = repo[None]
780 matchfiles = []
779 matchfiles = []
781 for f in m._files:
780 for f in m._files:
782 standin = lfutil.standin(f)
781 standin = lfutil.standin(f)
783 if standin in ctx or standin in mctx:
782 if standin in ctx or standin in mctx:
784 matchfiles.append(standin)
783 matchfiles.append(standin)
785 elif standin in wctx or lfdirstate[f] == 'r':
784 elif standin in wctx or lfdirstate[f] == 'r':
786 continue
785 continue
787 else:
786 else:
788 matchfiles.append(f)
787 matchfiles.append(f)
789 m._files = matchfiles
788 m._files = matchfiles
790 m._fileset = set(m._files)
789 m._fileset = set(m._files)
791 origmatchfn = m.matchfn
790 origmatchfn = m.matchfn
792 def matchfn(f):
791 def matchfn(f):
793 lfile = lfutil.splitstandin(f)
792 lfile = lfutil.splitstandin(f)
794 if lfile is not None:
793 if lfile is not None:
795 return (origmatchfn(lfile) and
794 return (origmatchfn(lfile) and
796 (f in ctx or f in mctx))
795 (f in ctx or f in mctx))
797 return origmatchfn(f)
796 return origmatchfn(f)
798 m.matchfn = matchfn
797 m.matchfn = matchfn
799 return m
798 return m
800 oldmatch = installmatchfn(overridematch)
799 oldmatch = installmatchfn(overridematch)
801 try:
800 try:
802 orig(ui, repo, ctx, parents, *pats, **opts)
801 orig(ui, repo, ctx, parents, *pats, **opts)
803 finally:
802 finally:
804 restorematchfn()
803 restorematchfn()
805
804
806 newstandins = lfutil.getstandinsstate(repo)
805 newstandins = lfutil.getstandinsstate(repo)
807 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
806 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
808 # lfdirstate should be 'normallookup'-ed for updated files,
807 # lfdirstate should be 'normallookup'-ed for updated files,
809 # because reverting doesn't touch dirstate for 'normal' files
808 # because reverting doesn't touch dirstate for 'normal' files
810 # when target revision is explicitly specified: in such case,
809 # when target revision is explicitly specified: in such case,
811 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
810 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
812 # of target (standin) file.
811 # of target (standin) file.
813 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
812 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
814 normallookup=True)
813 normallookup=True)
815
814
816 # after pulling changesets, we need to take some extra care to get
815 # after pulling changesets, we need to take some extra care to get
817 # largefiles updated remotely
816 # largefiles updated remotely
818 @eh.wrapcommand('pull',
817 @eh.wrapcommand('pull',
819 opts=[('', 'all-largefiles', None,
818 opts=[('', 'all-largefiles', None,
820 _('download all pulled versions of largefiles (DEPRECATED)')),
819 _('download all pulled versions of largefiles (DEPRECATED)')),
821 ('', 'lfrev', [],
820 ('', 'lfrev', [],
822 _('download largefiles for these revisions'), _('REV'))])
821 _('download largefiles for these revisions'), _('REV'))])
823 def overridepull(orig, ui, repo, source=None, **opts):
822 def overridepull(orig, ui, repo, source=None, **opts):
824 revsprepull = len(repo)
823 revsprepull = len(repo)
825 if not source:
824 if not source:
826 source = 'default'
825 source = 'default'
827 repo.lfpullsource = source
826 repo.lfpullsource = source
828 result = orig(ui, repo, source, **opts)
827 result = orig(ui, repo, source, **opts)
829 revspostpull = len(repo)
828 revspostpull = len(repo)
830 lfrevs = opts.get(r'lfrev', [])
829 lfrevs = opts.get(r'lfrev', [])
831 if opts.get(r'all_largefiles'):
830 if opts.get(r'all_largefiles'):
832 lfrevs.append('pulled()')
831 lfrevs.append('pulled()')
833 if lfrevs and revspostpull > revsprepull:
832 if lfrevs and revspostpull > revsprepull:
834 numcached = 0
833 numcached = 0
835 repo.firstpulled = revsprepull # for pulled() revset expression
834 repo.firstpulled = revsprepull # for pulled() revset expression
836 try:
835 try:
837 for rev in scmutil.revrange(repo, lfrevs):
836 for rev in scmutil.revrange(repo, lfrevs):
838 ui.note(_('pulling largefiles for revision %d\n') % rev)
837 ui.note(_('pulling largefiles for revision %d\n') % rev)
839 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
838 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
840 numcached += len(cached)
839 numcached += len(cached)
841 finally:
840 finally:
842 del repo.firstpulled
841 del repo.firstpulled
843 ui.status(_("%d largefiles cached\n") % numcached)
842 ui.status(_("%d largefiles cached\n") % numcached)
844 return result
843 return result
845
844
846 @eh.wrapcommand('push',
845 @eh.wrapcommand('push',
847 opts=[('', 'lfrev', [],
846 opts=[('', 'lfrev', [],
848 _('upload largefiles for these revisions'), _('REV'))])
847 _('upload largefiles for these revisions'), _('REV'))])
849 def overridepush(orig, ui, repo, *args, **kwargs):
848 def overridepush(orig, ui, repo, *args, **kwargs):
850 """Override push command and store --lfrev parameters in opargs"""
849 """Override push command and store --lfrev parameters in opargs"""
851 lfrevs = kwargs.pop(r'lfrev', None)
850 lfrevs = kwargs.pop(r'lfrev', None)
852 if lfrevs:
851 if lfrevs:
853 opargs = kwargs.setdefault(r'opargs', {})
852 opargs = kwargs.setdefault(r'opargs', {})
854 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
853 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
855 return orig(ui, repo, *args, **kwargs)
854 return orig(ui, repo, *args, **kwargs)
856
855
857 @eh.wrapfunction(exchange, 'pushoperation')
856 @eh.wrapfunction(exchange, 'pushoperation')
858 def exchangepushoperation(orig, *args, **kwargs):
857 def exchangepushoperation(orig, *args, **kwargs):
859 """Override pushoperation constructor and store lfrevs parameter"""
858 """Override pushoperation constructor and store lfrevs parameter"""
860 lfrevs = kwargs.pop(r'lfrevs', None)
859 lfrevs = kwargs.pop(r'lfrevs', None)
861 pushop = orig(*args, **kwargs)
860 pushop = orig(*args, **kwargs)
862 pushop.lfrevs = lfrevs
861 pushop.lfrevs = lfrevs
863 return pushop
862 return pushop
864
863
865 @eh.revsetpredicate('pulled()')
864 @eh.revsetpredicate('pulled()')
866 def pulledrevsetsymbol(repo, subset, x):
865 def pulledrevsetsymbol(repo, subset, x):
867 """Changesets that just has been pulled.
866 """Changesets that just has been pulled.
868
867
869 Only available with largefiles from pull --lfrev expressions.
868 Only available with largefiles from pull --lfrev expressions.
870
869
871 .. container:: verbose
870 .. container:: verbose
872
871
873 Some examples:
872 Some examples:
874
873
875 - pull largefiles for all new changesets::
874 - pull largefiles for all new changesets::
876
875
877 hg pull -lfrev "pulled()"
876 hg pull -lfrev "pulled()"
878
877
879 - pull largefiles for all new branch heads::
878 - pull largefiles for all new branch heads::
880
879
881 hg pull -lfrev "head(pulled()) and not closed()"
880 hg pull -lfrev "head(pulled()) and not closed()"
882
881
883 """
882 """
884
883
885 try:
884 try:
886 firstpulled = repo.firstpulled
885 firstpulled = repo.firstpulled
887 except AttributeError:
886 except AttributeError:
888 raise error.Abort(_("pulled() only available in --lfrev"))
887 raise error.Abort(_("pulled() only available in --lfrev"))
889 return smartset.baseset([r for r in subset if r >= firstpulled])
888 return smartset.baseset([r for r in subset if r >= firstpulled])
890
889
891 @eh.wrapcommand('clone',
890 @eh.wrapcommand('clone',
892 opts=[('', 'all-largefiles', None,
891 opts=[('', 'all-largefiles', None,
893 _('download all versions of all largefiles'))])
892 _('download all versions of all largefiles'))])
894 def overrideclone(orig, ui, source, dest=None, **opts):
893 def overrideclone(orig, ui, source, dest=None, **opts):
895 d = dest
894 d = dest
896 if d is None:
895 if d is None:
897 d = hg.defaultdest(source)
896 d = hg.defaultdest(source)
898 if opts.get(r'all_largefiles') and not hg.islocal(d):
897 if opts.get(r'all_largefiles') and not hg.islocal(d):
899 raise error.Abort(_(
898 raise error.Abort(_(
900 '--all-largefiles is incompatible with non-local destination %s') %
899 '--all-largefiles is incompatible with non-local destination %s') %
901 d)
900 d)
902
901
903 return orig(ui, source, dest, **opts)
902 return orig(ui, source, dest, **opts)
904
903
905 @eh.wrapfunction(hg, 'clone')
904 @eh.wrapfunction(hg, 'clone')
906 def hgclone(orig, ui, opts, *args, **kwargs):
905 def hgclone(orig, ui, opts, *args, **kwargs):
907 result = orig(ui, opts, *args, **kwargs)
906 result = orig(ui, opts, *args, **kwargs)
908
907
909 if result is not None:
908 if result is not None:
910 sourcerepo, destrepo = result
909 sourcerepo, destrepo = result
911 repo = destrepo.local()
910 repo = destrepo.local()
912
911
913 # When cloning to a remote repo (like through SSH), no repo is available
912 # When cloning to a remote repo (like through SSH), no repo is available
914 # from the peer. Therefore the largefiles can't be downloaded and the
913 # from the peer. Therefore the largefiles can't be downloaded and the
915 # hgrc can't be updated.
914 # hgrc can't be updated.
916 if not repo:
915 if not repo:
917 return result
916 return result
918
917
919 # Caching is implicitly limited to 'rev' option, since the dest repo was
918 # Caching is implicitly limited to 'rev' option, since the dest repo was
920 # truncated at that point. The user may expect a download count with
919 # truncated at that point. The user may expect a download count with
921 # this option, so attempt whether or not this is a largefile repo.
920 # this option, so attempt whether or not this is a largefile repo.
922 if opts.get('all_largefiles'):
921 if opts.get('all_largefiles'):
923 success, missing = lfcommands.downloadlfiles(ui, repo, None)
922 success, missing = lfcommands.downloadlfiles(ui, repo, None)
924
923
925 if missing != 0:
924 if missing != 0:
926 return None
925 return None
927
926
928 return result
927 return result
929
928
930 @eh.wrapcommand('rebase', extension='rebase')
929 @eh.wrapcommand('rebase', extension='rebase')
931 def overriderebase(orig, ui, repo, **opts):
930 def overriderebase(orig, ui, repo, **opts):
932 if not util.safehasattr(repo, '_largefilesenabled'):
931 if not util.safehasattr(repo, '_largefilesenabled'):
933 return orig(ui, repo, **opts)
932 return orig(ui, repo, **opts)
934
933
935 resuming = opts.get(r'continue')
934 resuming = opts.get(r'continue')
936 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
935 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
937 repo._lfstatuswriters.append(lambda *msg, **opts: None)
936 repo._lfstatuswriters.append(lambda *msg, **opts: None)
938 try:
937 try:
939 return orig(ui, repo, **opts)
938 return orig(ui, repo, **opts)
940 finally:
939 finally:
941 repo._lfstatuswriters.pop()
940 repo._lfstatuswriters.pop()
942 repo._lfcommithooks.pop()
941 repo._lfcommithooks.pop()
943
942
944 @eh.wrapcommand('archive')
943 @eh.wrapcommand('archive')
945 def overridearchivecmd(orig, ui, repo, dest, **opts):
944 def overridearchivecmd(orig, ui, repo, dest, **opts):
946 repo.unfiltered().lfstatus = True
945 repo.unfiltered().lfstatus = True
947
946
948 try:
947 try:
949 return orig(ui, repo.unfiltered(), dest, **opts)
948 return orig(ui, repo.unfiltered(), dest, **opts)
950 finally:
949 finally:
951 repo.unfiltered().lfstatus = False
950 repo.unfiltered().lfstatus = False
952
951
953 @eh.wrapfunction(webcommands, 'archive')
952 @eh.wrapfunction(webcommands, 'archive')
954 def hgwebarchive(orig, web):
953 def hgwebarchive(orig, web):
955 web.repo.lfstatus = True
954 web.repo.lfstatus = True
956
955
957 try:
956 try:
958 return orig(web)
957 return orig(web)
959 finally:
958 finally:
960 web.repo.lfstatus = False
959 web.repo.lfstatus = False
961
960
962 @eh.wrapfunction(archival, 'archive')
961 @eh.wrapfunction(archival, 'archive')
963 def overridearchive(orig, repo, dest, node, kind, decode=True, match=None,
962 def overridearchive(orig, repo, dest, node, kind, decode=True, match=None,
964 prefix='', mtime=None, subrepos=None):
963 prefix='', mtime=None, subrepos=None):
965 # For some reason setting repo.lfstatus in hgwebarchive only changes the
964 # For some reason setting repo.lfstatus in hgwebarchive only changes the
966 # unfiltered repo's attr, so check that as well.
965 # unfiltered repo's attr, so check that as well.
967 if not repo.lfstatus and not repo.unfiltered().lfstatus:
966 if not repo.lfstatus and not repo.unfiltered().lfstatus:
968 return orig(repo, dest, node, kind, decode, match, prefix, mtime,
967 return orig(repo, dest, node, kind, decode, match, prefix, mtime,
969 subrepos)
968 subrepos)
970
969
971 # No need to lock because we are only reading history and
970 # No need to lock because we are only reading history and
972 # largefile caches, neither of which are modified.
971 # largefile caches, neither of which are modified.
973 if node is not None:
972 if node is not None:
974 lfcommands.cachelfiles(repo.ui, repo, node)
973 lfcommands.cachelfiles(repo.ui, repo, node)
975
974
976 if kind not in archival.archivers:
975 if kind not in archival.archivers:
977 raise error.Abort(_("unknown archive type '%s'") % kind)
976 raise error.Abort(_("unknown archive type '%s'") % kind)
978
977
979 ctx = repo[node]
978 ctx = repo[node]
980
979
981 if kind == 'files':
980 if kind == 'files':
982 if prefix:
981 if prefix:
983 raise error.Abort(
982 raise error.Abort(
984 _('cannot give prefix when archiving to files'))
983 _('cannot give prefix when archiving to files'))
985 else:
984 else:
986 prefix = archival.tidyprefix(dest, kind, prefix)
985 prefix = archival.tidyprefix(dest, kind, prefix)
987
986
988 def write(name, mode, islink, getdata):
987 def write(name, mode, islink, getdata):
989 if match and not match(name):
988 if match and not match(name):
990 return
989 return
991 data = getdata()
990 data = getdata()
992 if decode:
991 if decode:
993 data = repo.wwritedata(name, data)
992 data = repo.wwritedata(name, data)
994 archiver.addfile(prefix + name, mode, islink, data)
993 archiver.addfile(prefix + name, mode, islink, data)
995
994
996 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
995 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
997
996
998 if repo.ui.configbool("ui", "archivemeta"):
997 if repo.ui.configbool("ui", "archivemeta"):
999 write('.hg_archival.txt', 0o644, False,
998 write('.hg_archival.txt', 0o644, False,
1000 lambda: archival.buildmetadata(ctx))
999 lambda: archival.buildmetadata(ctx))
1001
1000
1002 for f in ctx:
1001 for f in ctx:
1003 ff = ctx.flags(f)
1002 ff = ctx.flags(f)
1004 getdata = ctx[f].data
1003 getdata = ctx[f].data
1005 lfile = lfutil.splitstandin(f)
1004 lfile = lfutil.splitstandin(f)
1006 if lfile is not None:
1005 if lfile is not None:
1007 if node is not None:
1006 if node is not None:
1008 path = lfutil.findfile(repo, getdata().strip())
1007 path = lfutil.findfile(repo, getdata().strip())
1009
1008
1010 if path is None:
1009 if path is None:
1011 raise error.Abort(
1010 raise error.Abort(
1012 _('largefile %s not found in repo store or system cache')
1011 _('largefile %s not found in repo store or system cache')
1013 % lfile)
1012 % lfile)
1014 else:
1013 else:
1015 path = lfile
1014 path = lfile
1016
1015
1017 f = lfile
1016 f = lfile
1018
1017
1019 getdata = lambda: util.readfile(path)
1018 getdata = lambda: util.readfile(path)
1020 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1019 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1021
1020
1022 if subrepos:
1021 if subrepos:
1023 for subpath in sorted(ctx.substate):
1022 for subpath in sorted(ctx.substate):
1024 sub = ctx.workingsub(subpath)
1023 sub = ctx.workingsub(subpath)
1025 submatch = matchmod.subdirmatcher(subpath, match)
1024 submatch = matchmod.subdirmatcher(subpath, match)
1026 sub._repo.lfstatus = True
1025 sub._repo.lfstatus = True
1027 sub.archive(archiver, prefix, submatch)
1026 sub.archive(archiver, prefix, submatch)
1028
1027
1029 archiver.done()
1028 archiver.done()
1030
1029
1031 @eh.wrapfunction(subrepo.hgsubrepo, 'archive')
1030 @eh.wrapfunction(subrepo.hgsubrepo, 'archive')
1032 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1031 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1033 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1032 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1034 if not lfenabled or not repo._repo.lfstatus:
1033 if not lfenabled or not repo._repo.lfstatus:
1035 return orig(repo, archiver, prefix, match, decode)
1034 return orig(repo, archiver, prefix, match, decode)
1036
1035
1037 repo._get(repo._state + ('hg',))
1036 repo._get(repo._state + ('hg',))
1038 rev = repo._state[1]
1037 rev = repo._state[1]
1039 ctx = repo._repo[rev]
1038 ctx = repo._repo[rev]
1040
1039
1041 if ctx.node() is not None:
1040 if ctx.node() is not None:
1042 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1041 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1043
1042
1044 def write(name, mode, islink, getdata):
1043 def write(name, mode, islink, getdata):
1045 # At this point, the standin has been replaced with the largefile name,
1044 # At this point, the standin has been replaced with the largefile name,
1046 # so the normal matcher works here without the lfutil variants.
1045 # so the normal matcher works here without the lfutil variants.
1047 if match and not match(f):
1046 if match and not match(f):
1048 return
1047 return
1049 data = getdata()
1048 data = getdata()
1050 if decode:
1049 if decode:
1051 data = repo._repo.wwritedata(name, data)
1050 data = repo._repo.wwritedata(name, data)
1052
1051
1053 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
1052 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
1054
1053
1055 for f in ctx:
1054 for f in ctx:
1056 ff = ctx.flags(f)
1055 ff = ctx.flags(f)
1057 getdata = ctx[f].data
1056 getdata = ctx[f].data
1058 lfile = lfutil.splitstandin(f)
1057 lfile = lfutil.splitstandin(f)
1059 if lfile is not None:
1058 if lfile is not None:
1060 if ctx.node() is not None:
1059 if ctx.node() is not None:
1061 path = lfutil.findfile(repo._repo, getdata().strip())
1060 path = lfutil.findfile(repo._repo, getdata().strip())
1062
1061
1063 if path is None:
1062 if path is None:
1064 raise error.Abort(
1063 raise error.Abort(
1065 _('largefile %s not found in repo store or system cache')
1064 _('largefile %s not found in repo store or system cache')
1066 % lfile)
1065 % lfile)
1067 else:
1066 else:
1068 path = lfile
1067 path = lfile
1069
1068
1070 f = lfile
1069 f = lfile
1071
1070
1072 getdata = lambda: util.readfile(os.path.join(prefix, path))
1071 getdata = lambda: util.readfile(os.path.join(prefix, path))
1073
1072
1074 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1073 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1075
1074
1076 for subpath in sorted(ctx.substate):
1075 for subpath in sorted(ctx.substate):
1077 sub = ctx.workingsub(subpath)
1076 sub = ctx.workingsub(subpath)
1078 submatch = matchmod.subdirmatcher(subpath, match)
1077 submatch = matchmod.subdirmatcher(subpath, match)
1079 sub._repo.lfstatus = True
1078 sub._repo.lfstatus = True
1080 sub.archive(archiver, prefix + repo._path + '/', submatch, decode)
1079 sub.archive(archiver, prefix + repo._path + '/', submatch, decode)
1081
1080
1082 # If a largefile is modified, the change is not reflected in its
1081 # If a largefile is modified, the change is not reflected in its
1083 # standin until a commit. cmdutil.bailifchanged() raises an exception
1082 # standin until a commit. cmdutil.bailifchanged() raises an exception
1084 # if the repo has uncommitted changes. Wrap it to also check if
1083 # if the repo has uncommitted changes. Wrap it to also check if
1085 # largefiles were changed. This is used by bisect, backout and fetch.
1084 # largefiles were changed. This is used by bisect, backout and fetch.
1086 @eh.wrapfunction(cmdutil, 'bailifchanged')
1085 @eh.wrapfunction(cmdutil, 'bailifchanged')
1087 def overridebailifchanged(orig, repo, *args, **kwargs):
1086 def overridebailifchanged(orig, repo, *args, **kwargs):
1088 orig(repo, *args, **kwargs)
1087 orig(repo, *args, **kwargs)
1089 repo.lfstatus = True
1088 repo.lfstatus = True
1090 s = repo.status()
1089 s = repo.status()
1091 repo.lfstatus = False
1090 repo.lfstatus = False
1092 if s.modified or s.added or s.removed or s.deleted:
1091 if s.modified or s.added or s.removed or s.deleted:
1093 raise error.Abort(_('uncommitted changes'))
1092 raise error.Abort(_('uncommitted changes'))
1094
1093
1095 @eh.wrapfunction(cmdutil, 'postcommitstatus')
1094 @eh.wrapfunction(cmdutil, 'postcommitstatus')
1096 def postcommitstatus(orig, repo, *args, **kwargs):
1095 def postcommitstatus(orig, repo, *args, **kwargs):
1097 repo.lfstatus = True
1096 repo.lfstatus = True
1098 try:
1097 try:
1099 return orig(repo, *args, **kwargs)
1098 return orig(repo, *args, **kwargs)
1100 finally:
1099 finally:
1101 repo.lfstatus = False
1100 repo.lfstatus = False
1102
1101
1103 @eh.wrapfunction(cmdutil, 'forget')
1102 @eh.wrapfunction(cmdutil, 'forget')
1104 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun,
1103 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun,
1105 interactive):
1104 interactive):
1106 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1105 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1107 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun,
1106 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun,
1108 interactive)
1107 interactive)
1109 m = composelargefilematcher(match, repo[None].manifest())
1108 m = composelargefilematcher(match, repo[None].manifest())
1110
1109
1111 try:
1110 try:
1112 repo.lfstatus = True
1111 repo.lfstatus = True
1113 s = repo.status(match=m, clean=True)
1112 s = repo.status(match=m, clean=True)
1114 finally:
1113 finally:
1115 repo.lfstatus = False
1114 repo.lfstatus = False
1116 manifest = repo[None].manifest()
1115 manifest = repo[None].manifest()
1117 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1116 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1118 forget = [f for f in forget if lfutil.standin(f) in manifest]
1117 forget = [f for f in forget if lfutil.standin(f) in manifest]
1119
1118
1120 for f in forget:
1119 for f in forget:
1121 fstandin = lfutil.standin(f)
1120 fstandin = lfutil.standin(f)
1122 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1121 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1123 ui.warn(_('not removing %s: file is already untracked\n')
1122 ui.warn(_('not removing %s: file is already untracked\n')
1124 % m.rel(f))
1123 % m.rel(f))
1125 bad.append(f)
1124 bad.append(f)
1126
1125
1127 for f in forget:
1126 for f in forget:
1128 if ui.verbose or not m.exact(f):
1127 if ui.verbose or not m.exact(f):
1129 ui.status(_('removing %s\n') % m.rel(f))
1128 ui.status(_('removing %s\n') % m.rel(f))
1130
1129
1131 # Need to lock because standin files are deleted then removed from the
1130 # Need to lock because standin files are deleted then removed from the
1132 # repository and we could race in-between.
1131 # repository and we could race in-between.
1133 with repo.wlock():
1132 with repo.wlock():
1134 lfdirstate = lfutil.openlfdirstate(ui, repo)
1133 lfdirstate = lfutil.openlfdirstate(ui, repo)
1135 for f in forget:
1134 for f in forget:
1136 if lfdirstate[f] == 'a':
1135 if lfdirstate[f] == 'a':
1137 lfdirstate.drop(f)
1136 lfdirstate.drop(f)
1138 else:
1137 else:
1139 lfdirstate.remove(f)
1138 lfdirstate.remove(f)
1140 lfdirstate.write()
1139 lfdirstate.write()
1141 standins = [lfutil.standin(f) for f in forget]
1140 standins = [lfutil.standin(f) for f in forget]
1142 for f in standins:
1141 for f in standins:
1143 repo.wvfs.unlinkpath(f, ignoremissing=True)
1142 repo.wvfs.unlinkpath(f, ignoremissing=True)
1144 rejected = repo[None].forget(standins)
1143 rejected = repo[None].forget(standins)
1145
1144
1146 bad.extend(f for f in rejected if f in m.files())
1145 bad.extend(f for f in rejected if f in m.files())
1147 forgot.extend(f for f in forget if f not in rejected)
1146 forgot.extend(f for f in forget if f not in rejected)
1148 return bad, forgot
1147 return bad, forgot
1149
1148
1150 def _getoutgoings(repo, other, missing, addfunc):
1149 def _getoutgoings(repo, other, missing, addfunc):
1151 """get pairs of filename and largefile hash in outgoing revisions
1150 """get pairs of filename and largefile hash in outgoing revisions
1152 in 'missing'.
1151 in 'missing'.
1153
1152
1154 largefiles already existing on 'other' repository are ignored.
1153 largefiles already existing on 'other' repository are ignored.
1155
1154
1156 'addfunc' is invoked with each unique pairs of filename and
1155 'addfunc' is invoked with each unique pairs of filename and
1157 largefile hash value.
1156 largefile hash value.
1158 """
1157 """
1159 knowns = set()
1158 knowns = set()
1160 lfhashes = set()
1159 lfhashes = set()
1161 def dedup(fn, lfhash):
1160 def dedup(fn, lfhash):
1162 k = (fn, lfhash)
1161 k = (fn, lfhash)
1163 if k not in knowns:
1162 if k not in knowns:
1164 knowns.add(k)
1163 knowns.add(k)
1165 lfhashes.add(lfhash)
1164 lfhashes.add(lfhash)
1166 lfutil.getlfilestoupload(repo, missing, dedup)
1165 lfutil.getlfilestoupload(repo, missing, dedup)
1167 if lfhashes:
1166 if lfhashes:
1168 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1167 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1169 for fn, lfhash in knowns:
1168 for fn, lfhash in knowns:
1170 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1169 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1171 addfunc(fn, lfhash)
1170 addfunc(fn, lfhash)
1172
1171
1173 def outgoinghook(ui, repo, other, opts, missing):
1172 def outgoinghook(ui, repo, other, opts, missing):
1174 if opts.pop('large', None):
1173 if opts.pop('large', None):
1175 lfhashes = set()
1174 lfhashes = set()
1176 if ui.debugflag:
1175 if ui.debugflag:
1177 toupload = {}
1176 toupload = {}
1178 def addfunc(fn, lfhash):
1177 def addfunc(fn, lfhash):
1179 if fn not in toupload:
1178 if fn not in toupload:
1180 toupload[fn] = []
1179 toupload[fn] = []
1181 toupload[fn].append(lfhash)
1180 toupload[fn].append(lfhash)
1182 lfhashes.add(lfhash)
1181 lfhashes.add(lfhash)
1183 def showhashes(fn):
1182 def showhashes(fn):
1184 for lfhash in sorted(toupload[fn]):
1183 for lfhash in sorted(toupload[fn]):
1185 ui.debug(' %s\n' % (lfhash))
1184 ui.debug(' %s\n' % (lfhash))
1186 else:
1185 else:
1187 toupload = set()
1186 toupload = set()
1188 def addfunc(fn, lfhash):
1187 def addfunc(fn, lfhash):
1189 toupload.add(fn)
1188 toupload.add(fn)
1190 lfhashes.add(lfhash)
1189 lfhashes.add(lfhash)
1191 def showhashes(fn):
1190 def showhashes(fn):
1192 pass
1191 pass
1193 _getoutgoings(repo, other, missing, addfunc)
1192 _getoutgoings(repo, other, missing, addfunc)
1194
1193
1195 if not toupload:
1194 if not toupload:
1196 ui.status(_('largefiles: no files to upload\n'))
1195 ui.status(_('largefiles: no files to upload\n'))
1197 else:
1196 else:
1198 ui.status(_('largefiles to upload (%d entities):\n')
1197 ui.status(_('largefiles to upload (%d entities):\n')
1199 % (len(lfhashes)))
1198 % (len(lfhashes)))
1200 for file in sorted(toupload):
1199 for file in sorted(toupload):
1201 ui.status(lfutil.splitstandin(file) + '\n')
1200 ui.status(lfutil.splitstandin(file) + '\n')
1202 showhashes(file)
1201 showhashes(file)
1203 ui.status('\n')
1202 ui.status('\n')
1204
1203
1205 @eh.wrapcommand('outgoing',
1204 @eh.wrapcommand('outgoing',
1206 opts=[('', 'large', None, _('display outgoing largefiles'))])
1205 opts=[('', 'large', None, _('display outgoing largefiles'))])
1207 def _outgoingcmd(orig, *args, **kwargs):
1206 def _outgoingcmd(orig, *args, **kwargs):
1208 # Nothing to do here other than add the extra help option- the hook above
1207 # Nothing to do here other than add the extra help option- the hook above
1209 # processes it.
1208 # processes it.
1210 return orig(*args, **kwargs)
1209 return orig(*args, **kwargs)
1211
1210
1212 def summaryremotehook(ui, repo, opts, changes):
1211 def summaryremotehook(ui, repo, opts, changes):
1213 largeopt = opts.get('large', False)
1212 largeopt = opts.get('large', False)
1214 if changes is None:
1213 if changes is None:
1215 if largeopt:
1214 if largeopt:
1216 return (False, True) # only outgoing check is needed
1215 return (False, True) # only outgoing check is needed
1217 else:
1216 else:
1218 return (False, False)
1217 return (False, False)
1219 elif largeopt:
1218 elif largeopt:
1220 url, branch, peer, outgoing = changes[1]
1219 url, branch, peer, outgoing = changes[1]
1221 if peer is None:
1220 if peer is None:
1222 # i18n: column positioning for "hg summary"
1221 # i18n: column positioning for "hg summary"
1223 ui.status(_('largefiles: (no remote repo)\n'))
1222 ui.status(_('largefiles: (no remote repo)\n'))
1224 return
1223 return
1225
1224
1226 toupload = set()
1225 toupload = set()
1227 lfhashes = set()
1226 lfhashes = set()
1228 def addfunc(fn, lfhash):
1227 def addfunc(fn, lfhash):
1229 toupload.add(fn)
1228 toupload.add(fn)
1230 lfhashes.add(lfhash)
1229 lfhashes.add(lfhash)
1231 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1230 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1232
1231
1233 if not toupload:
1232 if not toupload:
1234 # i18n: column positioning for "hg summary"
1233 # i18n: column positioning for "hg summary"
1235 ui.status(_('largefiles: (no files to upload)\n'))
1234 ui.status(_('largefiles: (no files to upload)\n'))
1236 else:
1235 else:
1237 # i18n: column positioning for "hg summary"
1236 # i18n: column positioning for "hg summary"
1238 ui.status(_('largefiles: %d entities for %d files to upload\n')
1237 ui.status(_('largefiles: %d entities for %d files to upload\n')
1239 % (len(lfhashes), len(toupload)))
1238 % (len(lfhashes), len(toupload)))
1240
1239
1241 @eh.wrapcommand('summary',
1240 @eh.wrapcommand('summary',
1242 opts=[('', 'large', None, _('display outgoing largefiles'))])
1241 opts=[('', 'large', None, _('display outgoing largefiles'))])
1243 def overridesummary(orig, ui, repo, *pats, **opts):
1242 def overridesummary(orig, ui, repo, *pats, **opts):
1244 try:
1243 try:
1245 repo.lfstatus = True
1244 repo.lfstatus = True
1246 orig(ui, repo, *pats, **opts)
1245 orig(ui, repo, *pats, **opts)
1247 finally:
1246 finally:
1248 repo.lfstatus = False
1247 repo.lfstatus = False
1249
1248
1250 @eh.wrapfunction(scmutil, 'addremove')
1249 @eh.wrapfunction(scmutil, 'addremove')
1251 def scmutiladdremove(orig, repo, matcher, prefix, opts=None):
1250 def scmutiladdremove(orig, repo, matcher, prefix, opts=None):
1252 if opts is None:
1251 if opts is None:
1253 opts = {}
1252 opts = {}
1254 if not lfutil.islfilesrepo(repo):
1253 if not lfutil.islfilesrepo(repo):
1255 return orig(repo, matcher, prefix, opts)
1254 return orig(repo, matcher, prefix, opts)
1256 # Get the list of missing largefiles so we can remove them
1255 # Get the list of missing largefiles so we can remove them
1257 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1256 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1258 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
1257 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
1259 subrepos=[], ignored=False, clean=False,
1258 subrepos=[], ignored=False, clean=False,
1260 unknown=False)
1259 unknown=False)
1261
1260
1262 # Call into the normal remove code, but the removing of the standin, we want
1261 # Call into the normal remove code, but the removing of the standin, we want
1263 # to have handled by original addremove. Monkey patching here makes sure
1262 # to have handled by original addremove. Monkey patching here makes sure
1264 # we don't remove the standin in the largefiles code, preventing a very
1263 # we don't remove the standin in the largefiles code, preventing a very
1265 # confused state later.
1264 # confused state later.
1266 if s.deleted:
1265 if s.deleted:
1267 m = copy.copy(matcher)
1266 m = copy.copy(matcher)
1268
1267
1269 # The m._files and m._map attributes are not changed to the deleted list
1268 # The m._files and m._map attributes are not changed to the deleted list
1270 # because that affects the m.exact() test, which in turn governs whether
1269 # because that affects the m.exact() test, which in turn governs whether
1271 # or not the file name is printed, and how. Simply limit the original
1270 # or not the file name is printed, and how. Simply limit the original
1272 # matches to those in the deleted status list.
1271 # matches to those in the deleted status list.
1273 matchfn = m.matchfn
1272 matchfn = m.matchfn
1274 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1273 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1275
1274
1276 removelargefiles(repo.ui, repo, True, m, opts.get('dry_run'),
1275 removelargefiles(repo.ui, repo, True, m, opts.get('dry_run'),
1277 **pycompat.strkwargs(opts))
1276 **pycompat.strkwargs(opts))
1278 # Call into the normal add code, and any files that *should* be added as
1277 # Call into the normal add code, and any files that *should* be added as
1279 # largefiles will be
1278 # largefiles will be
1280 added, bad = addlargefiles(repo.ui, repo, True, matcher,
1279 added, bad = addlargefiles(repo.ui, repo, True, matcher,
1281 **pycompat.strkwargs(opts))
1280 **pycompat.strkwargs(opts))
1282 # Now that we've handled largefiles, hand off to the original addremove
1281 # Now that we've handled largefiles, hand off to the original addremove
1283 # function to take care of the rest. Make sure it doesn't do anything with
1282 # function to take care of the rest. Make sure it doesn't do anything with
1284 # largefiles by passing a matcher that will ignore them.
1283 # largefiles by passing a matcher that will ignore them.
1285 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1284 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1286 return orig(repo, matcher, prefix, opts)
1285 return orig(repo, matcher, prefix, opts)
1287
1286
1288 # Calling purge with --all will cause the largefiles to be deleted.
1287 # Calling purge with --all will cause the largefiles to be deleted.
1289 # Override repo.status to prevent this from happening.
1288 # Override repo.status to prevent this from happening.
1290 @eh.wrapcommand('purge', extension='purge')
1289 @eh.wrapcommand('purge', extension='purge')
1291 def overridepurge(orig, ui, repo, *dirs, **opts):
1290 def overridepurge(orig, ui, repo, *dirs, **opts):
1292 # XXX Monkey patching a repoview will not work. The assigned attribute will
1291 # XXX Monkey patching a repoview will not work. The assigned attribute will
1293 # be set on the unfiltered repo, but we will only lookup attributes in the
1292 # be set on the unfiltered repo, but we will only lookup attributes in the
1294 # unfiltered repo if the lookup in the repoview object itself fails. As the
1293 # unfiltered repo if the lookup in the repoview object itself fails. As the
1295 # monkey patched method exists on the repoview class the lookup will not
1294 # monkey patched method exists on the repoview class the lookup will not
1296 # fail. As a result, the original version will shadow the monkey patched
1295 # fail. As a result, the original version will shadow the monkey patched
1297 # one, defeating the monkey patch.
1296 # one, defeating the monkey patch.
1298 #
1297 #
1299 # As a work around we use an unfiltered repo here. We should do something
1298 # As a work around we use an unfiltered repo here. We should do something
1300 # cleaner instead.
1299 # cleaner instead.
1301 repo = repo.unfiltered()
1300 repo = repo.unfiltered()
1302 oldstatus = repo.status
1301 oldstatus = repo.status
1303 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1302 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1304 clean=False, unknown=False, listsubrepos=False):
1303 clean=False, unknown=False, listsubrepos=False):
1305 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1304 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1306 listsubrepos)
1305 listsubrepos)
1307 lfdirstate = lfutil.openlfdirstate(ui, repo)
1306 lfdirstate = lfutil.openlfdirstate(ui, repo)
1308 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1307 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1309 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1308 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1310 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1309 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1311 unknown, ignored, r.clean)
1310 unknown, ignored, r.clean)
1312 repo.status = overridestatus
1311 repo.status = overridestatus
1313 orig(ui, repo, *dirs, **opts)
1312 orig(ui, repo, *dirs, **opts)
1314 repo.status = oldstatus
1313 repo.status = oldstatus
1315
1314
1316 @eh.wrapcommand('rollback')
1315 @eh.wrapcommand('rollback')
1317 def overriderollback(orig, ui, repo, **opts):
1316 def overriderollback(orig, ui, repo, **opts):
1318 with repo.wlock():
1317 with repo.wlock():
1319 before = repo.dirstate.parents()
1318 before = repo.dirstate.parents()
1320 orphans = set(f for f in repo.dirstate
1319 orphans = set(f for f in repo.dirstate
1321 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1320 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1322 result = orig(ui, repo, **opts)
1321 result = orig(ui, repo, **opts)
1323 after = repo.dirstate.parents()
1322 after = repo.dirstate.parents()
1324 if before == after:
1323 if before == after:
1325 return result # no need to restore standins
1324 return result # no need to restore standins
1326
1325
1327 pctx = repo['.']
1326 pctx = repo['.']
1328 for f in repo.dirstate:
1327 for f in repo.dirstate:
1329 if lfutil.isstandin(f):
1328 if lfutil.isstandin(f):
1330 orphans.discard(f)
1329 orphans.discard(f)
1331 if repo.dirstate[f] == 'r':
1330 if repo.dirstate[f] == 'r':
1332 repo.wvfs.unlinkpath(f, ignoremissing=True)
1331 repo.wvfs.unlinkpath(f, ignoremissing=True)
1333 elif f in pctx:
1332 elif f in pctx:
1334 fctx = pctx[f]
1333 fctx = pctx[f]
1335 repo.wwrite(f, fctx.data(), fctx.flags())
1334 repo.wwrite(f, fctx.data(), fctx.flags())
1336 else:
1335 else:
1337 # content of standin is not so important in 'a',
1336 # content of standin is not so important in 'a',
1338 # 'm' or 'n' (coming from the 2nd parent) cases
1337 # 'm' or 'n' (coming from the 2nd parent) cases
1339 lfutil.writestandin(repo, f, '', False)
1338 lfutil.writestandin(repo, f, '', False)
1340 for standin in orphans:
1339 for standin in orphans:
1341 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1340 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1342
1341
1343 lfdirstate = lfutil.openlfdirstate(ui, repo)
1342 lfdirstate = lfutil.openlfdirstate(ui, repo)
1344 orphans = set(lfdirstate)
1343 orphans = set(lfdirstate)
1345 lfiles = lfutil.listlfiles(repo)
1344 lfiles = lfutil.listlfiles(repo)
1346 for file in lfiles:
1345 for file in lfiles:
1347 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1346 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1348 orphans.discard(file)
1347 orphans.discard(file)
1349 for lfile in orphans:
1348 for lfile in orphans:
1350 lfdirstate.drop(lfile)
1349 lfdirstate.drop(lfile)
1351 lfdirstate.write()
1350 lfdirstate.write()
1352 return result
1351 return result
1353
1352
1354 @eh.wrapcommand('transplant', extension='transplant')
1353 @eh.wrapcommand('transplant', extension='transplant')
1355 def overridetransplant(orig, ui, repo, *revs, **opts):
1354 def overridetransplant(orig, ui, repo, *revs, **opts):
1356 resuming = opts.get(r'continue')
1355 resuming = opts.get(r'continue')
1357 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1356 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1358 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1357 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1359 try:
1358 try:
1360 result = orig(ui, repo, *revs, **opts)
1359 result = orig(ui, repo, *revs, **opts)
1361 finally:
1360 finally:
1362 repo._lfstatuswriters.pop()
1361 repo._lfstatuswriters.pop()
1363 repo._lfcommithooks.pop()
1362 repo._lfcommithooks.pop()
1364 return result
1363 return result
1365
1364
1366 @eh.wrapcommand('cat')
1365 @eh.wrapcommand('cat')
1367 def overridecat(orig, ui, repo, file1, *pats, **opts):
1366 def overridecat(orig, ui, repo, file1, *pats, **opts):
1368 opts = pycompat.byteskwargs(opts)
1367 opts = pycompat.byteskwargs(opts)
1369 ctx = scmutil.revsingle(repo, opts.get('rev'))
1368 ctx = scmutil.revsingle(repo, opts.get('rev'))
1370 err = 1
1369 err = 1
1371 notbad = set()
1370 notbad = set()
1372 m = scmutil.match(ctx, (file1,) + pats, opts)
1371 m = scmutil.match(ctx, (file1,) + pats, opts)
1373 origmatchfn = m.matchfn
1372 origmatchfn = m.matchfn
1374 def lfmatchfn(f):
1373 def lfmatchfn(f):
1375 if origmatchfn(f):
1374 if origmatchfn(f):
1376 return True
1375 return True
1377 lf = lfutil.splitstandin(f)
1376 lf = lfutil.splitstandin(f)
1378 if lf is None:
1377 if lf is None:
1379 return False
1378 return False
1380 notbad.add(lf)
1379 notbad.add(lf)
1381 return origmatchfn(lf)
1380 return origmatchfn(lf)
1382 m.matchfn = lfmatchfn
1381 m.matchfn = lfmatchfn
1383 origbadfn = m.bad
1382 origbadfn = m.bad
1384 def lfbadfn(f, msg):
1383 def lfbadfn(f, msg):
1385 if not f in notbad:
1384 if not f in notbad:
1386 origbadfn(f, msg)
1385 origbadfn(f, msg)
1387 m.bad = lfbadfn
1386 m.bad = lfbadfn
1388
1387
1389 origvisitdirfn = m.visitdir
1388 origvisitdirfn = m.visitdir
1390 def lfvisitdirfn(dir):
1389 def lfvisitdirfn(dir):
1391 if dir == lfutil.shortname:
1390 if dir == lfutil.shortname:
1392 return True
1391 return True
1393 ret = origvisitdirfn(dir)
1392 ret = origvisitdirfn(dir)
1394 if ret:
1393 if ret:
1395 return ret
1394 return ret
1396 lf = lfutil.splitstandin(dir)
1395 lf = lfutil.splitstandin(dir)
1397 if lf is None:
1396 if lf is None:
1398 return False
1397 return False
1399 return origvisitdirfn(lf)
1398 return origvisitdirfn(lf)
1400 m.visitdir = lfvisitdirfn
1399 m.visitdir = lfvisitdirfn
1401
1400
1402 for f in ctx.walk(m):
1401 for f in ctx.walk(m):
1403 with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp:
1402 with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp:
1404 lf = lfutil.splitstandin(f)
1403 lf = lfutil.splitstandin(f)
1405 if lf is None or origmatchfn(f):
1404 if lf is None or origmatchfn(f):
1406 # duplicating unreachable code from commands.cat
1405 # duplicating unreachable code from commands.cat
1407 data = ctx[f].data()
1406 data = ctx[f].data()
1408 if opts.get('decode'):
1407 if opts.get('decode'):
1409 data = repo.wwritedata(f, data)
1408 data = repo.wwritedata(f, data)
1410 fp.write(data)
1409 fp.write(data)
1411 else:
1410 else:
1412 hash = lfutil.readasstandin(ctx[f])
1411 hash = lfutil.readasstandin(ctx[f])
1413 if not lfutil.inusercache(repo.ui, hash):
1412 if not lfutil.inusercache(repo.ui, hash):
1414 store = storefactory.openstore(repo)
1413 store = storefactory.openstore(repo)
1415 success, missing = store.get([(lf, hash)])
1414 success, missing = store.get([(lf, hash)])
1416 if len(success) != 1:
1415 if len(success) != 1:
1417 raise error.Abort(
1416 raise error.Abort(
1418 _('largefile %s is not in cache and could not be '
1417 _('largefile %s is not in cache and could not be '
1419 'downloaded') % lf)
1418 'downloaded') % lf)
1420 path = lfutil.usercachepath(repo.ui, hash)
1419 path = lfutil.usercachepath(repo.ui, hash)
1421 with open(path, "rb") as fpin:
1420 with open(path, "rb") as fpin:
1422 for chunk in util.filechunkiter(fpin):
1421 for chunk in util.filechunkiter(fpin):
1423 fp.write(chunk)
1422 fp.write(chunk)
1424 err = 0
1423 err = 0
1425 return err
1424 return err
1426
1425
1427 @eh.wrapfunction(merge, 'update')
1426 @eh.wrapfunction(merge, 'update')
1428 def mergeupdate(orig, repo, node, branchmerge, force,
1427 def mergeupdate(orig, repo, node, branchmerge, force,
1429 *args, **kwargs):
1428 *args, **kwargs):
1430 matcher = kwargs.get(r'matcher', None)
1429 matcher = kwargs.get(r'matcher', None)
1431 # note if this is a partial update
1430 # note if this is a partial update
1432 partial = matcher and not matcher.always()
1431 partial = matcher and not matcher.always()
1433 with repo.wlock():
1432 with repo.wlock():
1434 # branch | | |
1433 # branch | | |
1435 # merge | force | partial | action
1434 # merge | force | partial | action
1436 # -------+-------+---------+--------------
1435 # -------+-------+---------+--------------
1437 # x | x | x | linear-merge
1436 # x | x | x | linear-merge
1438 # o | x | x | branch-merge
1437 # o | x | x | branch-merge
1439 # x | o | x | overwrite (as clean update)
1438 # x | o | x | overwrite (as clean update)
1440 # o | o | x | force-branch-merge (*1)
1439 # o | o | x | force-branch-merge (*1)
1441 # x | x | o | (*)
1440 # x | x | o | (*)
1442 # o | x | o | (*)
1441 # o | x | o | (*)
1443 # x | o | o | overwrite (as revert)
1442 # x | o | o | overwrite (as revert)
1444 # o | o | o | (*)
1443 # o | o | o | (*)
1445 #
1444 #
1446 # (*) don't care
1445 # (*) don't care
1447 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1446 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1448
1447
1449 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1448 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1450 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1449 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1451 repo.getcwd()),
1450 repo.getcwd()),
1452 subrepos=[], ignored=False,
1451 subrepos=[], ignored=False,
1453 clean=True, unknown=False)
1452 clean=True, unknown=False)
1454 oldclean = set(s.clean)
1453 oldclean = set(s.clean)
1455 pctx = repo['.']
1454 pctx = repo['.']
1456 dctx = repo[node]
1455 dctx = repo[node]
1457 for lfile in unsure + s.modified:
1456 for lfile in unsure + s.modified:
1458 lfileabs = repo.wvfs.join(lfile)
1457 lfileabs = repo.wvfs.join(lfile)
1459 if not repo.wvfs.exists(lfileabs):
1458 if not repo.wvfs.exists(lfileabs):
1460 continue
1459 continue
1461 lfhash = lfutil.hashfile(lfileabs)
1460 lfhash = lfutil.hashfile(lfileabs)
1462 standin = lfutil.standin(lfile)
1461 standin = lfutil.standin(lfile)
1463 lfutil.writestandin(repo, standin, lfhash,
1462 lfutil.writestandin(repo, standin, lfhash,
1464 lfutil.getexecutable(lfileabs))
1463 lfutil.getexecutable(lfileabs))
1465 if (standin in pctx and
1464 if (standin in pctx and
1466 lfhash == lfutil.readasstandin(pctx[standin])):
1465 lfhash == lfutil.readasstandin(pctx[standin])):
1467 oldclean.add(lfile)
1466 oldclean.add(lfile)
1468 for lfile in s.added:
1467 for lfile in s.added:
1469 fstandin = lfutil.standin(lfile)
1468 fstandin = lfutil.standin(lfile)
1470 if fstandin not in dctx:
1469 if fstandin not in dctx:
1471 # in this case, content of standin file is meaningless
1470 # in this case, content of standin file is meaningless
1472 # (in dctx, lfile is unknown, or normal file)
1471 # (in dctx, lfile is unknown, or normal file)
1473 continue
1472 continue
1474 lfutil.updatestandin(repo, lfile, fstandin)
1473 lfutil.updatestandin(repo, lfile, fstandin)
1475 # mark all clean largefiles as dirty, just in case the update gets
1474 # mark all clean largefiles as dirty, just in case the update gets
1476 # interrupted before largefiles and lfdirstate are synchronized
1475 # interrupted before largefiles and lfdirstate are synchronized
1477 for lfile in oldclean:
1476 for lfile in oldclean:
1478 lfdirstate.normallookup(lfile)
1477 lfdirstate.normallookup(lfile)
1479 lfdirstate.write()
1478 lfdirstate.write()
1480
1479
1481 oldstandins = lfutil.getstandinsstate(repo)
1480 oldstandins = lfutil.getstandinsstate(repo)
1482 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1481 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1483 # good candidate for in-memory merge (large files, custom dirstate,
1482 # good candidate for in-memory merge (large files, custom dirstate,
1484 # matcher usage).
1483 # matcher usage).
1485 kwargs[r'wc'] = repo[None]
1484 kwargs[r'wc'] = repo[None]
1486 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1485 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1487
1486
1488 newstandins = lfutil.getstandinsstate(repo)
1487 newstandins = lfutil.getstandinsstate(repo)
1489 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1488 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1490
1489
1491 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1490 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1492 # all the ones that didn't change as clean
1491 # all the ones that didn't change as clean
1493 for lfile in oldclean.difference(filelist):
1492 for lfile in oldclean.difference(filelist):
1494 lfdirstate.normal(lfile)
1493 lfdirstate.normal(lfile)
1495 lfdirstate.write()
1494 lfdirstate.write()
1496
1495
1497 if branchmerge or force or partial:
1496 if branchmerge or force or partial:
1498 filelist.extend(s.deleted + s.removed)
1497 filelist.extend(s.deleted + s.removed)
1499
1498
1500 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1499 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1501 normallookup=partial)
1500 normallookup=partial)
1502
1501
1503 return result
1502 return result
1504
1503
1505 @eh.wrapfunction(scmutil, 'marktouched')
1504 @eh.wrapfunction(scmutil, 'marktouched')
1506 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1505 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1507 result = orig(repo, files, *args, **kwargs)
1506 result = orig(repo, files, *args, **kwargs)
1508
1507
1509 filelist = []
1508 filelist = []
1510 for f in files:
1509 for f in files:
1511 lf = lfutil.splitstandin(f)
1510 lf = lfutil.splitstandin(f)
1512 if lf is not None:
1511 if lf is not None:
1513 filelist.append(lf)
1512 filelist.append(lf)
1514 if filelist:
1513 if filelist:
1515 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1514 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1516 printmessage=False, normallookup=True)
1515 printmessage=False, normallookup=True)
1517
1516
1518 return result
1517 return result
1519
1518
1520 @eh.wrapfunction(upgrade, 'preservedrequirements')
1519 @eh.wrapfunction(upgrade, 'preservedrequirements')
1521 @eh.wrapfunction(upgrade, 'supporteddestrequirements')
1520 @eh.wrapfunction(upgrade, 'supporteddestrequirements')
1522 def upgraderequirements(orig, repo):
1521 def upgraderequirements(orig, repo):
1523 reqs = orig(repo)
1522 reqs = orig(repo)
1524 if 'largefiles' in repo.requirements:
1523 if 'largefiles' in repo.requirements:
1525 reqs.add('largefiles')
1524 reqs.add('largefiles')
1526 return reqs
1525 return reqs
1527
1526
1528 _lfscheme = 'largefile://'
1527 _lfscheme = 'largefile://'
1529
1528
1530 @eh.wrapfunction(urlmod, 'open')
1529 @eh.wrapfunction(urlmod, 'open')
1531 def openlargefile(orig, ui, url_, data=None):
1530 def openlargefile(orig, ui, url_, data=None):
1532 if url_.startswith(_lfscheme):
1531 if url_.startswith(_lfscheme):
1533 if data:
1532 if data:
1534 msg = "cannot use data on a 'largefile://' url"
1533 msg = "cannot use data on a 'largefile://' url"
1535 raise error.ProgrammingError(msg)
1534 raise error.ProgrammingError(msg)
1536 lfid = url_[len(_lfscheme):]
1535 lfid = url_[len(_lfscheme):]
1537 return storefactory.getlfile(ui, lfid)
1536 return storefactory.getlfile(ui, lfid)
1538 else:
1537 else:
1539 return orig(ui, url_, data=data)
1538 return orig(ui, url_, data=data)
General Comments 0
You need to be logged in to leave comments. Login now