##// END OF EJS Templates
largefiles: use wrappedfunction() for util.copyfile() override...
Martin von Zweigbergk -
r41720:028bb170 default
parent child Browse files
Show More
@@ -1,1544 +1,1539 b''
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(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 = oldmatch(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)
681 oldmatch = installmatchfn(overridematch)
682 listpats = []
682 listpats = []
683 for pat in pats:
683 for pat in pats:
684 if matchmod.patkind(pat) is not None:
684 if matchmod.patkind(pat) is not None:
685 listpats.append(pat)
685 listpats.append(pat)
686 else:
686 else:
687 listpats.append(makestandin(pat))
687 listpats.append(makestandin(pat))
688
688
689 try:
690 origcopyfile = util.copyfile
691 copiedfiles = []
689 copiedfiles = []
692 def overridecopyfile(src, dest, *args, **kwargs):
690 def overridecopyfile(orig, src, dest, *args, **kwargs):
693 if (lfutil.shortname in src and
691 if (lfutil.shortname in src and
694 dest.startswith(repo.wjoin(lfutil.shortname))):
692 dest.startswith(repo.wjoin(lfutil.shortname))):
695 destlfile = dest.replace(lfutil.shortname, '')
693 destlfile = dest.replace(lfutil.shortname, '')
696 if not opts['force'] and os.path.exists(destlfile):
694 if not opts['force'] and os.path.exists(destlfile):
697 raise IOError('',
695 raise IOError('',
698 _('destination largefile already exists'))
696 _('destination largefile already exists'))
699 copiedfiles.append((src, dest))
697 copiedfiles.append((src, dest))
700 origcopyfile(src, dest, *args, **kwargs)
698 orig(src, dest, *args, **kwargs)
701
699 with extensions.wrappedfunction(util, 'copyfile', overridecopyfile):
702 util.copyfile = overridecopyfile
703 result += orig(ui, repo, listpats, opts, rename)
700 result += orig(ui, repo, listpats, opts, rename)
704 finally:
705 util.copyfile = origcopyfile
706
701
707 lfdirstate = lfutil.openlfdirstate(ui, repo)
702 lfdirstate = lfutil.openlfdirstate(ui, repo)
708 for (src, dest) in copiedfiles:
703 for (src, dest) in copiedfiles:
709 if (lfutil.shortname in src and
704 if (lfutil.shortname in src and
710 dest.startswith(repo.wjoin(lfutil.shortname))):
705 dest.startswith(repo.wjoin(lfutil.shortname))):
711 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
706 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
712 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
707 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
713 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
708 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
714 if not os.path.isdir(destlfiledir):
709 if not os.path.isdir(destlfiledir):
715 os.makedirs(destlfiledir)
710 os.makedirs(destlfiledir)
716 if rename:
711 if rename:
717 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
712 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
718
713
719 # The file is gone, but this deletes any empty parent
714 # The file is gone, but this deletes any empty parent
720 # directories as a side-effect.
715 # directories as a side-effect.
721 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
716 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
722 lfdirstate.remove(srclfile)
717 lfdirstate.remove(srclfile)
723 else:
718 else:
724 util.copyfile(repo.wjoin(srclfile),
719 util.copyfile(repo.wjoin(srclfile),
725 repo.wjoin(destlfile))
720 repo.wjoin(destlfile))
726
721
727 lfdirstate.add(destlfile)
722 lfdirstate.add(destlfile)
728 lfdirstate.write()
723 lfdirstate.write()
729 except error.Abort as e:
724 except error.Abort as e:
730 if pycompat.bytestr(e) != _('no files to copy'):
725 if pycompat.bytestr(e) != _('no files to copy'):
731 raise e
726 raise e
732 else:
727 else:
733 nolfiles = True
728 nolfiles = True
734 finally:
729 finally:
735 restorematchfn()
730 restorematchfn()
736 wlock.release()
731 wlock.release()
737
732
738 if nolfiles and nonormalfiles:
733 if nolfiles and nonormalfiles:
739 raise error.Abort(_('no files to copy'))
734 raise error.Abort(_('no files to copy'))
740
735
741 return result
736 return result
742
737
743 # When the user calls revert, we have to be careful to not revert any
738 # When the user calls revert, we have to be careful to not revert any
744 # changes to other largefiles accidentally. This means we have to keep
739 # changes to other largefiles accidentally. This means we have to keep
745 # track of the largefiles that are being reverted so we only pull down
740 # track of the largefiles that are being reverted so we only pull down
746 # the necessary largefiles.
741 # the necessary largefiles.
747 #
742 #
748 # Standins are only updated (to match the hash of largefiles) before
743 # Standins are only updated (to match the hash of largefiles) before
749 # commits. Update the standins then run the original revert, changing
744 # commits. Update the standins then run the original revert, changing
750 # the matcher to hit standins instead of largefiles. Based on the
745 # the matcher to hit standins instead of largefiles. Based on the
751 # resulting standins update the largefiles.
746 # resulting standins update the largefiles.
752 @eh.wrapfunction(cmdutil, 'revert')
747 @eh.wrapfunction(cmdutil, 'revert')
753 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
748 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
754 # Because we put the standins in a bad state (by updating them)
749 # Because we put the standins in a bad state (by updating them)
755 # and then return them to a correct state we need to lock to
750 # and then return them to a correct state we need to lock to
756 # prevent others from changing them in their incorrect state.
751 # prevent others from changing them in their incorrect state.
757 with repo.wlock():
752 with repo.wlock():
758 lfdirstate = lfutil.openlfdirstate(ui, repo)
753 lfdirstate = lfutil.openlfdirstate(ui, repo)
759 s = lfutil.lfdirstatestatus(lfdirstate, repo)
754 s = lfutil.lfdirstatestatus(lfdirstate, repo)
760 lfdirstate.write()
755 lfdirstate.write()
761 for lfile in s.modified:
756 for lfile in s.modified:
762 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
757 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
763 for lfile in s.deleted:
758 for lfile in s.deleted:
764 fstandin = lfutil.standin(lfile)
759 fstandin = lfutil.standin(lfile)
765 if (repo.wvfs.exists(fstandin)):
760 if (repo.wvfs.exists(fstandin)):
766 repo.wvfs.unlink(fstandin)
761 repo.wvfs.unlink(fstandin)
767
762
768 oldstandins = lfutil.getstandinsstate(repo)
763 oldstandins = lfutil.getstandinsstate(repo)
769
764
770 def overridematch(mctx, pats=(), opts=None, globbed=False,
765 def overridematch(mctx, pats=(), opts=None, globbed=False,
771 default='relpath', badfn=None):
766 default='relpath', badfn=None):
772 if opts is None:
767 if opts is None:
773 opts = {}
768 opts = {}
774 match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn)
769 match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn)
775 m = copy.copy(match)
770 m = copy.copy(match)
776
771
777 # revert supports recursing into subrepos, and though largefiles
772 # revert supports recursing into subrepos, and though largefiles
778 # currently doesn't work correctly in that case, this match is
773 # currently doesn't work correctly in that case, this match is
779 # called, so the lfdirstate above may not be the correct one for
774 # called, so the lfdirstate above may not be the correct one for
780 # this invocation of match.
775 # this invocation of match.
781 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
776 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
782 False)
777 False)
783
778
784 wctx = repo[None]
779 wctx = repo[None]
785 matchfiles = []
780 matchfiles = []
786 for f in m._files:
781 for f in m._files:
787 standin = lfutil.standin(f)
782 standin = lfutil.standin(f)
788 if standin in ctx or standin in mctx:
783 if standin in ctx or standin in mctx:
789 matchfiles.append(standin)
784 matchfiles.append(standin)
790 elif standin in wctx or lfdirstate[f] == 'r':
785 elif standin in wctx or lfdirstate[f] == 'r':
791 continue
786 continue
792 else:
787 else:
793 matchfiles.append(f)
788 matchfiles.append(f)
794 m._files = matchfiles
789 m._files = matchfiles
795 m._fileset = set(m._files)
790 m._fileset = set(m._files)
796 origmatchfn = m.matchfn
791 origmatchfn = m.matchfn
797 def matchfn(f):
792 def matchfn(f):
798 lfile = lfutil.splitstandin(f)
793 lfile = lfutil.splitstandin(f)
799 if lfile is not None:
794 if lfile is not None:
800 return (origmatchfn(lfile) and
795 return (origmatchfn(lfile) and
801 (f in ctx or f in mctx))
796 (f in ctx or f in mctx))
802 return origmatchfn(f)
797 return origmatchfn(f)
803 m.matchfn = matchfn
798 m.matchfn = matchfn
804 return m
799 return m
805 oldmatch = installmatchfn(overridematch)
800 oldmatch = installmatchfn(overridematch)
806 try:
801 try:
807 orig(ui, repo, ctx, parents, *pats, **opts)
802 orig(ui, repo, ctx, parents, *pats, **opts)
808 finally:
803 finally:
809 restorematchfn()
804 restorematchfn()
810
805
811 newstandins = lfutil.getstandinsstate(repo)
806 newstandins = lfutil.getstandinsstate(repo)
812 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
807 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
813 # lfdirstate should be 'normallookup'-ed for updated files,
808 # lfdirstate should be 'normallookup'-ed for updated files,
814 # because reverting doesn't touch dirstate for 'normal' files
809 # because reverting doesn't touch dirstate for 'normal' files
815 # when target revision is explicitly specified: in such case,
810 # when target revision is explicitly specified: in such case,
816 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
811 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
817 # of target (standin) file.
812 # of target (standin) file.
818 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
813 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
819 normallookup=True)
814 normallookup=True)
820
815
821 # after pulling changesets, we need to take some extra care to get
816 # after pulling changesets, we need to take some extra care to get
822 # largefiles updated remotely
817 # largefiles updated remotely
823 @eh.wrapcommand('pull',
818 @eh.wrapcommand('pull',
824 opts=[('', 'all-largefiles', None,
819 opts=[('', 'all-largefiles', None,
825 _('download all pulled versions of largefiles (DEPRECATED)')),
820 _('download all pulled versions of largefiles (DEPRECATED)')),
826 ('', 'lfrev', [],
821 ('', 'lfrev', [],
827 _('download largefiles for these revisions'), _('REV'))])
822 _('download largefiles for these revisions'), _('REV'))])
828 def overridepull(orig, ui, repo, source=None, **opts):
823 def overridepull(orig, ui, repo, source=None, **opts):
829 revsprepull = len(repo)
824 revsprepull = len(repo)
830 if not source:
825 if not source:
831 source = 'default'
826 source = 'default'
832 repo.lfpullsource = source
827 repo.lfpullsource = source
833 result = orig(ui, repo, source, **opts)
828 result = orig(ui, repo, source, **opts)
834 revspostpull = len(repo)
829 revspostpull = len(repo)
835 lfrevs = opts.get(r'lfrev', [])
830 lfrevs = opts.get(r'lfrev', [])
836 if opts.get(r'all_largefiles'):
831 if opts.get(r'all_largefiles'):
837 lfrevs.append('pulled()')
832 lfrevs.append('pulled()')
838 if lfrevs and revspostpull > revsprepull:
833 if lfrevs and revspostpull > revsprepull:
839 numcached = 0
834 numcached = 0
840 repo.firstpulled = revsprepull # for pulled() revset expression
835 repo.firstpulled = revsprepull # for pulled() revset expression
841 try:
836 try:
842 for rev in scmutil.revrange(repo, lfrevs):
837 for rev in scmutil.revrange(repo, lfrevs):
843 ui.note(_('pulling largefiles for revision %d\n') % rev)
838 ui.note(_('pulling largefiles for revision %d\n') % rev)
844 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
839 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
845 numcached += len(cached)
840 numcached += len(cached)
846 finally:
841 finally:
847 del repo.firstpulled
842 del repo.firstpulled
848 ui.status(_("%d largefiles cached\n") % numcached)
843 ui.status(_("%d largefiles cached\n") % numcached)
849 return result
844 return result
850
845
851 @eh.wrapcommand('push',
846 @eh.wrapcommand('push',
852 opts=[('', 'lfrev', [],
847 opts=[('', 'lfrev', [],
853 _('upload largefiles for these revisions'), _('REV'))])
848 _('upload largefiles for these revisions'), _('REV'))])
854 def overridepush(orig, ui, repo, *args, **kwargs):
849 def overridepush(orig, ui, repo, *args, **kwargs):
855 """Override push command and store --lfrev parameters in opargs"""
850 """Override push command and store --lfrev parameters in opargs"""
856 lfrevs = kwargs.pop(r'lfrev', None)
851 lfrevs = kwargs.pop(r'lfrev', None)
857 if lfrevs:
852 if lfrevs:
858 opargs = kwargs.setdefault(r'opargs', {})
853 opargs = kwargs.setdefault(r'opargs', {})
859 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
854 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
860 return orig(ui, repo, *args, **kwargs)
855 return orig(ui, repo, *args, **kwargs)
861
856
862 @eh.wrapfunction(exchange, 'pushoperation')
857 @eh.wrapfunction(exchange, 'pushoperation')
863 def exchangepushoperation(orig, *args, **kwargs):
858 def exchangepushoperation(orig, *args, **kwargs):
864 """Override pushoperation constructor and store lfrevs parameter"""
859 """Override pushoperation constructor and store lfrevs parameter"""
865 lfrevs = kwargs.pop(r'lfrevs', None)
860 lfrevs = kwargs.pop(r'lfrevs', None)
866 pushop = orig(*args, **kwargs)
861 pushop = orig(*args, **kwargs)
867 pushop.lfrevs = lfrevs
862 pushop.lfrevs = lfrevs
868 return pushop
863 return pushop
869
864
870 @eh.revsetpredicate('pulled()')
865 @eh.revsetpredicate('pulled()')
871 def pulledrevsetsymbol(repo, subset, x):
866 def pulledrevsetsymbol(repo, subset, x):
872 """Changesets that just has been pulled.
867 """Changesets that just has been pulled.
873
868
874 Only available with largefiles from pull --lfrev expressions.
869 Only available with largefiles from pull --lfrev expressions.
875
870
876 .. container:: verbose
871 .. container:: verbose
877
872
878 Some examples:
873 Some examples:
879
874
880 - pull largefiles for all new changesets::
875 - pull largefiles for all new changesets::
881
876
882 hg pull -lfrev "pulled()"
877 hg pull -lfrev "pulled()"
883
878
884 - pull largefiles for all new branch heads::
879 - pull largefiles for all new branch heads::
885
880
886 hg pull -lfrev "head(pulled()) and not closed()"
881 hg pull -lfrev "head(pulled()) and not closed()"
887
882
888 """
883 """
889
884
890 try:
885 try:
891 firstpulled = repo.firstpulled
886 firstpulled = repo.firstpulled
892 except AttributeError:
887 except AttributeError:
893 raise error.Abort(_("pulled() only available in --lfrev"))
888 raise error.Abort(_("pulled() only available in --lfrev"))
894 return smartset.baseset([r for r in subset if r >= firstpulled])
889 return smartset.baseset([r for r in subset if r >= firstpulled])
895
890
896 @eh.wrapcommand('clone',
891 @eh.wrapcommand('clone',
897 opts=[('', 'all-largefiles', None,
892 opts=[('', 'all-largefiles', None,
898 _('download all versions of all largefiles'))])
893 _('download all versions of all largefiles'))])
899 def overrideclone(orig, ui, source, dest=None, **opts):
894 def overrideclone(orig, ui, source, dest=None, **opts):
900 d = dest
895 d = dest
901 if d is None:
896 if d is None:
902 d = hg.defaultdest(source)
897 d = hg.defaultdest(source)
903 if opts.get(r'all_largefiles') and not hg.islocal(d):
898 if opts.get(r'all_largefiles') and not hg.islocal(d):
904 raise error.Abort(_(
899 raise error.Abort(_(
905 '--all-largefiles is incompatible with non-local destination %s') %
900 '--all-largefiles is incompatible with non-local destination %s') %
906 d)
901 d)
907
902
908 return orig(ui, source, dest, **opts)
903 return orig(ui, source, dest, **opts)
909
904
910 @eh.wrapfunction(hg, 'clone')
905 @eh.wrapfunction(hg, 'clone')
911 def hgclone(orig, ui, opts, *args, **kwargs):
906 def hgclone(orig, ui, opts, *args, **kwargs):
912 result = orig(ui, opts, *args, **kwargs)
907 result = orig(ui, opts, *args, **kwargs)
913
908
914 if result is not None:
909 if result is not None:
915 sourcerepo, destrepo = result
910 sourcerepo, destrepo = result
916 repo = destrepo.local()
911 repo = destrepo.local()
917
912
918 # When cloning to a remote repo (like through SSH), no repo is available
913 # When cloning to a remote repo (like through SSH), no repo is available
919 # from the peer. Therefore the largefiles can't be downloaded and the
914 # from the peer. Therefore the largefiles can't be downloaded and the
920 # hgrc can't be updated.
915 # hgrc can't be updated.
921 if not repo:
916 if not repo:
922 return result
917 return result
923
918
924 # Caching is implicitly limited to 'rev' option, since the dest repo was
919 # Caching is implicitly limited to 'rev' option, since the dest repo was
925 # truncated at that point. The user may expect a download count with
920 # truncated at that point. The user may expect a download count with
926 # this option, so attempt whether or not this is a largefile repo.
921 # this option, so attempt whether or not this is a largefile repo.
927 if opts.get('all_largefiles'):
922 if opts.get('all_largefiles'):
928 success, missing = lfcommands.downloadlfiles(ui, repo, None)
923 success, missing = lfcommands.downloadlfiles(ui, repo, None)
929
924
930 if missing != 0:
925 if missing != 0:
931 return None
926 return None
932
927
933 return result
928 return result
934
929
935 @eh.wrapcommand('rebase', extension='rebase')
930 @eh.wrapcommand('rebase', extension='rebase')
936 def overriderebase(orig, ui, repo, **opts):
931 def overriderebase(orig, ui, repo, **opts):
937 if not util.safehasattr(repo, '_largefilesenabled'):
932 if not util.safehasattr(repo, '_largefilesenabled'):
938 return orig(ui, repo, **opts)
933 return orig(ui, repo, **opts)
939
934
940 resuming = opts.get(r'continue')
935 resuming = opts.get(r'continue')
941 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
936 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
942 repo._lfstatuswriters.append(lambda *msg, **opts: None)
937 repo._lfstatuswriters.append(lambda *msg, **opts: None)
943 try:
938 try:
944 return orig(ui, repo, **opts)
939 return orig(ui, repo, **opts)
945 finally:
940 finally:
946 repo._lfstatuswriters.pop()
941 repo._lfstatuswriters.pop()
947 repo._lfcommithooks.pop()
942 repo._lfcommithooks.pop()
948
943
949 @eh.wrapcommand('archive')
944 @eh.wrapcommand('archive')
950 def overridearchivecmd(orig, ui, repo, dest, **opts):
945 def overridearchivecmd(orig, ui, repo, dest, **opts):
951 repo.unfiltered().lfstatus = True
946 repo.unfiltered().lfstatus = True
952
947
953 try:
948 try:
954 return orig(ui, repo.unfiltered(), dest, **opts)
949 return orig(ui, repo.unfiltered(), dest, **opts)
955 finally:
950 finally:
956 repo.unfiltered().lfstatus = False
951 repo.unfiltered().lfstatus = False
957
952
958 @eh.wrapfunction(webcommands, 'archive')
953 @eh.wrapfunction(webcommands, 'archive')
959 def hgwebarchive(orig, web):
954 def hgwebarchive(orig, web):
960 web.repo.lfstatus = True
955 web.repo.lfstatus = True
961
956
962 try:
957 try:
963 return orig(web)
958 return orig(web)
964 finally:
959 finally:
965 web.repo.lfstatus = False
960 web.repo.lfstatus = False
966
961
967 @eh.wrapfunction(archival, 'archive')
962 @eh.wrapfunction(archival, 'archive')
968 def overridearchive(orig, repo, dest, node, kind, decode=True, match=None,
963 def overridearchive(orig, repo, dest, node, kind, decode=True, match=None,
969 prefix='', mtime=None, subrepos=None):
964 prefix='', mtime=None, subrepos=None):
970 # For some reason setting repo.lfstatus in hgwebarchive only changes the
965 # For some reason setting repo.lfstatus in hgwebarchive only changes the
971 # unfiltered repo's attr, so check that as well.
966 # unfiltered repo's attr, so check that as well.
972 if not repo.lfstatus and not repo.unfiltered().lfstatus:
967 if not repo.lfstatus and not repo.unfiltered().lfstatus:
973 return orig(repo, dest, node, kind, decode, match, prefix, mtime,
968 return orig(repo, dest, node, kind, decode, match, prefix, mtime,
974 subrepos)
969 subrepos)
975
970
976 # No need to lock because we are only reading history and
971 # No need to lock because we are only reading history and
977 # largefile caches, neither of which are modified.
972 # largefile caches, neither of which are modified.
978 if node is not None:
973 if node is not None:
979 lfcommands.cachelfiles(repo.ui, repo, node)
974 lfcommands.cachelfiles(repo.ui, repo, node)
980
975
981 if kind not in archival.archivers:
976 if kind not in archival.archivers:
982 raise error.Abort(_("unknown archive type '%s'") % kind)
977 raise error.Abort(_("unknown archive type '%s'") % kind)
983
978
984 ctx = repo[node]
979 ctx = repo[node]
985
980
986 if kind == 'files':
981 if kind == 'files':
987 if prefix:
982 if prefix:
988 raise error.Abort(
983 raise error.Abort(
989 _('cannot give prefix when archiving to files'))
984 _('cannot give prefix when archiving to files'))
990 else:
985 else:
991 prefix = archival.tidyprefix(dest, kind, prefix)
986 prefix = archival.tidyprefix(dest, kind, prefix)
992
987
993 def write(name, mode, islink, getdata):
988 def write(name, mode, islink, getdata):
994 if match and not match(name):
989 if match and not match(name):
995 return
990 return
996 data = getdata()
991 data = getdata()
997 if decode:
992 if decode:
998 data = repo.wwritedata(name, data)
993 data = repo.wwritedata(name, data)
999 archiver.addfile(prefix + name, mode, islink, data)
994 archiver.addfile(prefix + name, mode, islink, data)
1000
995
1001 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
996 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
1002
997
1003 if repo.ui.configbool("ui", "archivemeta"):
998 if repo.ui.configbool("ui", "archivemeta"):
1004 write('.hg_archival.txt', 0o644, False,
999 write('.hg_archival.txt', 0o644, False,
1005 lambda: archival.buildmetadata(ctx))
1000 lambda: archival.buildmetadata(ctx))
1006
1001
1007 for f in ctx:
1002 for f in ctx:
1008 ff = ctx.flags(f)
1003 ff = ctx.flags(f)
1009 getdata = ctx[f].data
1004 getdata = ctx[f].data
1010 lfile = lfutil.splitstandin(f)
1005 lfile = lfutil.splitstandin(f)
1011 if lfile is not None:
1006 if lfile is not None:
1012 if node is not None:
1007 if node is not None:
1013 path = lfutil.findfile(repo, getdata().strip())
1008 path = lfutil.findfile(repo, getdata().strip())
1014
1009
1015 if path is None:
1010 if path is None:
1016 raise error.Abort(
1011 raise error.Abort(
1017 _('largefile %s not found in repo store or system cache')
1012 _('largefile %s not found in repo store or system cache')
1018 % lfile)
1013 % lfile)
1019 else:
1014 else:
1020 path = lfile
1015 path = lfile
1021
1016
1022 f = lfile
1017 f = lfile
1023
1018
1024 getdata = lambda: util.readfile(path)
1019 getdata = lambda: util.readfile(path)
1025 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1020 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1026
1021
1027 if subrepos:
1022 if subrepos:
1028 for subpath in sorted(ctx.substate):
1023 for subpath in sorted(ctx.substate):
1029 sub = ctx.workingsub(subpath)
1024 sub = ctx.workingsub(subpath)
1030 submatch = matchmod.subdirmatcher(subpath, match)
1025 submatch = matchmod.subdirmatcher(subpath, match)
1031 sub._repo.lfstatus = True
1026 sub._repo.lfstatus = True
1032 sub.archive(archiver, prefix, submatch)
1027 sub.archive(archiver, prefix, submatch)
1033
1028
1034 archiver.done()
1029 archiver.done()
1035
1030
1036 @eh.wrapfunction(subrepo.hgsubrepo, 'archive')
1031 @eh.wrapfunction(subrepo.hgsubrepo, 'archive')
1037 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1032 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1038 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1033 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1039 if not lfenabled or not repo._repo.lfstatus:
1034 if not lfenabled or not repo._repo.lfstatus:
1040 return orig(repo, archiver, prefix, match, decode)
1035 return orig(repo, archiver, prefix, match, decode)
1041
1036
1042 repo._get(repo._state + ('hg',))
1037 repo._get(repo._state + ('hg',))
1043 rev = repo._state[1]
1038 rev = repo._state[1]
1044 ctx = repo._repo[rev]
1039 ctx = repo._repo[rev]
1045
1040
1046 if ctx.node() is not None:
1041 if ctx.node() is not None:
1047 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1042 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1048
1043
1049 def write(name, mode, islink, getdata):
1044 def write(name, mode, islink, getdata):
1050 # At this point, the standin has been replaced with the largefile name,
1045 # At this point, the standin has been replaced with the largefile name,
1051 # so the normal matcher works here without the lfutil variants.
1046 # so the normal matcher works here without the lfutil variants.
1052 if match and not match(f):
1047 if match and not match(f):
1053 return
1048 return
1054 data = getdata()
1049 data = getdata()
1055 if decode:
1050 if decode:
1056 data = repo._repo.wwritedata(name, data)
1051 data = repo._repo.wwritedata(name, data)
1057
1052
1058 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
1053 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
1059
1054
1060 for f in ctx:
1055 for f in ctx:
1061 ff = ctx.flags(f)
1056 ff = ctx.flags(f)
1062 getdata = ctx[f].data
1057 getdata = ctx[f].data
1063 lfile = lfutil.splitstandin(f)
1058 lfile = lfutil.splitstandin(f)
1064 if lfile is not None:
1059 if lfile is not None:
1065 if ctx.node() is not None:
1060 if ctx.node() is not None:
1066 path = lfutil.findfile(repo._repo, getdata().strip())
1061 path = lfutil.findfile(repo._repo, getdata().strip())
1067
1062
1068 if path is None:
1063 if path is None:
1069 raise error.Abort(
1064 raise error.Abort(
1070 _('largefile %s not found in repo store or system cache')
1065 _('largefile %s not found in repo store or system cache')
1071 % lfile)
1066 % lfile)
1072 else:
1067 else:
1073 path = lfile
1068 path = lfile
1074
1069
1075 f = lfile
1070 f = lfile
1076
1071
1077 getdata = lambda: util.readfile(os.path.join(prefix, path))
1072 getdata = lambda: util.readfile(os.path.join(prefix, path))
1078
1073
1079 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1074 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1080
1075
1081 for subpath in sorted(ctx.substate):
1076 for subpath in sorted(ctx.substate):
1082 sub = ctx.workingsub(subpath)
1077 sub = ctx.workingsub(subpath)
1083 submatch = matchmod.subdirmatcher(subpath, match)
1078 submatch = matchmod.subdirmatcher(subpath, match)
1084 sub._repo.lfstatus = True
1079 sub._repo.lfstatus = True
1085 sub.archive(archiver, prefix + repo._path + '/', submatch, decode)
1080 sub.archive(archiver, prefix + repo._path + '/', submatch, decode)
1086
1081
1087 # If a largefile is modified, the change is not reflected in its
1082 # If a largefile is modified, the change is not reflected in its
1088 # standin until a commit. cmdutil.bailifchanged() raises an exception
1083 # standin until a commit. cmdutil.bailifchanged() raises an exception
1089 # if the repo has uncommitted changes. Wrap it to also check if
1084 # if the repo has uncommitted changes. Wrap it to also check if
1090 # largefiles were changed. This is used by bisect, backout and fetch.
1085 # largefiles were changed. This is used by bisect, backout and fetch.
1091 @eh.wrapfunction(cmdutil, 'bailifchanged')
1086 @eh.wrapfunction(cmdutil, 'bailifchanged')
1092 def overridebailifchanged(orig, repo, *args, **kwargs):
1087 def overridebailifchanged(orig, repo, *args, **kwargs):
1093 orig(repo, *args, **kwargs)
1088 orig(repo, *args, **kwargs)
1094 repo.lfstatus = True
1089 repo.lfstatus = True
1095 s = repo.status()
1090 s = repo.status()
1096 repo.lfstatus = False
1091 repo.lfstatus = False
1097 if s.modified or s.added or s.removed or s.deleted:
1092 if s.modified or s.added or s.removed or s.deleted:
1098 raise error.Abort(_('uncommitted changes'))
1093 raise error.Abort(_('uncommitted changes'))
1099
1094
1100 @eh.wrapfunction(cmdutil, 'postcommitstatus')
1095 @eh.wrapfunction(cmdutil, 'postcommitstatus')
1101 def postcommitstatus(orig, repo, *args, **kwargs):
1096 def postcommitstatus(orig, repo, *args, **kwargs):
1102 repo.lfstatus = True
1097 repo.lfstatus = True
1103 try:
1098 try:
1104 return orig(repo, *args, **kwargs)
1099 return orig(repo, *args, **kwargs)
1105 finally:
1100 finally:
1106 repo.lfstatus = False
1101 repo.lfstatus = False
1107
1102
1108 @eh.wrapfunction(cmdutil, 'forget')
1103 @eh.wrapfunction(cmdutil, 'forget')
1109 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun,
1104 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun,
1110 interactive):
1105 interactive):
1111 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1106 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1112 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun,
1107 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun,
1113 interactive)
1108 interactive)
1114 m = composelargefilematcher(match, repo[None].manifest())
1109 m = composelargefilematcher(match, repo[None].manifest())
1115
1110
1116 try:
1111 try:
1117 repo.lfstatus = True
1112 repo.lfstatus = True
1118 s = repo.status(match=m, clean=True)
1113 s = repo.status(match=m, clean=True)
1119 finally:
1114 finally:
1120 repo.lfstatus = False
1115 repo.lfstatus = False
1121 manifest = repo[None].manifest()
1116 manifest = repo[None].manifest()
1122 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1117 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1123 forget = [f for f in forget if lfutil.standin(f) in manifest]
1118 forget = [f for f in forget if lfutil.standin(f) in manifest]
1124
1119
1125 for f in forget:
1120 for f in forget:
1126 fstandin = lfutil.standin(f)
1121 fstandin = lfutil.standin(f)
1127 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1122 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1128 ui.warn(_('not removing %s: file is already untracked\n')
1123 ui.warn(_('not removing %s: file is already untracked\n')
1129 % m.rel(f))
1124 % m.rel(f))
1130 bad.append(f)
1125 bad.append(f)
1131
1126
1132 for f in forget:
1127 for f in forget:
1133 if ui.verbose or not m.exact(f):
1128 if ui.verbose or not m.exact(f):
1134 ui.status(_('removing %s\n') % m.rel(f))
1129 ui.status(_('removing %s\n') % m.rel(f))
1135
1130
1136 # Need to lock because standin files are deleted then removed from the
1131 # Need to lock because standin files are deleted then removed from the
1137 # repository and we could race in-between.
1132 # repository and we could race in-between.
1138 with repo.wlock():
1133 with repo.wlock():
1139 lfdirstate = lfutil.openlfdirstate(ui, repo)
1134 lfdirstate = lfutil.openlfdirstate(ui, repo)
1140 for f in forget:
1135 for f in forget:
1141 if lfdirstate[f] == 'a':
1136 if lfdirstate[f] == 'a':
1142 lfdirstate.drop(f)
1137 lfdirstate.drop(f)
1143 else:
1138 else:
1144 lfdirstate.remove(f)
1139 lfdirstate.remove(f)
1145 lfdirstate.write()
1140 lfdirstate.write()
1146 standins = [lfutil.standin(f) for f in forget]
1141 standins = [lfutil.standin(f) for f in forget]
1147 for f in standins:
1142 for f in standins:
1148 repo.wvfs.unlinkpath(f, ignoremissing=True)
1143 repo.wvfs.unlinkpath(f, ignoremissing=True)
1149 rejected = repo[None].forget(standins)
1144 rejected = repo[None].forget(standins)
1150
1145
1151 bad.extend(f for f in rejected if f in m.files())
1146 bad.extend(f for f in rejected if f in m.files())
1152 forgot.extend(f for f in forget if f not in rejected)
1147 forgot.extend(f for f in forget if f not in rejected)
1153 return bad, forgot
1148 return bad, forgot
1154
1149
1155 def _getoutgoings(repo, other, missing, addfunc):
1150 def _getoutgoings(repo, other, missing, addfunc):
1156 """get pairs of filename and largefile hash in outgoing revisions
1151 """get pairs of filename and largefile hash in outgoing revisions
1157 in 'missing'.
1152 in 'missing'.
1158
1153
1159 largefiles already existing on 'other' repository are ignored.
1154 largefiles already existing on 'other' repository are ignored.
1160
1155
1161 'addfunc' is invoked with each unique pairs of filename and
1156 'addfunc' is invoked with each unique pairs of filename and
1162 largefile hash value.
1157 largefile hash value.
1163 """
1158 """
1164 knowns = set()
1159 knowns = set()
1165 lfhashes = set()
1160 lfhashes = set()
1166 def dedup(fn, lfhash):
1161 def dedup(fn, lfhash):
1167 k = (fn, lfhash)
1162 k = (fn, lfhash)
1168 if k not in knowns:
1163 if k not in knowns:
1169 knowns.add(k)
1164 knowns.add(k)
1170 lfhashes.add(lfhash)
1165 lfhashes.add(lfhash)
1171 lfutil.getlfilestoupload(repo, missing, dedup)
1166 lfutil.getlfilestoupload(repo, missing, dedup)
1172 if lfhashes:
1167 if lfhashes:
1173 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1168 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1174 for fn, lfhash in knowns:
1169 for fn, lfhash in knowns:
1175 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1170 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1176 addfunc(fn, lfhash)
1171 addfunc(fn, lfhash)
1177
1172
1178 def outgoinghook(ui, repo, other, opts, missing):
1173 def outgoinghook(ui, repo, other, opts, missing):
1179 if opts.pop('large', None):
1174 if opts.pop('large', None):
1180 lfhashes = set()
1175 lfhashes = set()
1181 if ui.debugflag:
1176 if ui.debugflag:
1182 toupload = {}
1177 toupload = {}
1183 def addfunc(fn, lfhash):
1178 def addfunc(fn, lfhash):
1184 if fn not in toupload:
1179 if fn not in toupload:
1185 toupload[fn] = []
1180 toupload[fn] = []
1186 toupload[fn].append(lfhash)
1181 toupload[fn].append(lfhash)
1187 lfhashes.add(lfhash)
1182 lfhashes.add(lfhash)
1188 def showhashes(fn):
1183 def showhashes(fn):
1189 for lfhash in sorted(toupload[fn]):
1184 for lfhash in sorted(toupload[fn]):
1190 ui.debug(' %s\n' % (lfhash))
1185 ui.debug(' %s\n' % (lfhash))
1191 else:
1186 else:
1192 toupload = set()
1187 toupload = set()
1193 def addfunc(fn, lfhash):
1188 def addfunc(fn, lfhash):
1194 toupload.add(fn)
1189 toupload.add(fn)
1195 lfhashes.add(lfhash)
1190 lfhashes.add(lfhash)
1196 def showhashes(fn):
1191 def showhashes(fn):
1197 pass
1192 pass
1198 _getoutgoings(repo, other, missing, addfunc)
1193 _getoutgoings(repo, other, missing, addfunc)
1199
1194
1200 if not toupload:
1195 if not toupload:
1201 ui.status(_('largefiles: no files to upload\n'))
1196 ui.status(_('largefiles: no files to upload\n'))
1202 else:
1197 else:
1203 ui.status(_('largefiles to upload (%d entities):\n')
1198 ui.status(_('largefiles to upload (%d entities):\n')
1204 % (len(lfhashes)))
1199 % (len(lfhashes)))
1205 for file in sorted(toupload):
1200 for file in sorted(toupload):
1206 ui.status(lfutil.splitstandin(file) + '\n')
1201 ui.status(lfutil.splitstandin(file) + '\n')
1207 showhashes(file)
1202 showhashes(file)
1208 ui.status('\n')
1203 ui.status('\n')
1209
1204
1210 @eh.wrapcommand('outgoing',
1205 @eh.wrapcommand('outgoing',
1211 opts=[('', 'large', None, _('display outgoing largefiles'))])
1206 opts=[('', 'large', None, _('display outgoing largefiles'))])
1212 def _outgoingcmd(orig, *args, **kwargs):
1207 def _outgoingcmd(orig, *args, **kwargs):
1213 # Nothing to do here other than add the extra help option- the hook above
1208 # Nothing to do here other than add the extra help option- the hook above
1214 # processes it.
1209 # processes it.
1215 return orig(*args, **kwargs)
1210 return orig(*args, **kwargs)
1216
1211
1217 def summaryremotehook(ui, repo, opts, changes):
1212 def summaryremotehook(ui, repo, opts, changes):
1218 largeopt = opts.get('large', False)
1213 largeopt = opts.get('large', False)
1219 if changes is None:
1214 if changes is None:
1220 if largeopt:
1215 if largeopt:
1221 return (False, True) # only outgoing check is needed
1216 return (False, True) # only outgoing check is needed
1222 else:
1217 else:
1223 return (False, False)
1218 return (False, False)
1224 elif largeopt:
1219 elif largeopt:
1225 url, branch, peer, outgoing = changes[1]
1220 url, branch, peer, outgoing = changes[1]
1226 if peer is None:
1221 if peer is None:
1227 # i18n: column positioning for "hg summary"
1222 # i18n: column positioning for "hg summary"
1228 ui.status(_('largefiles: (no remote repo)\n'))
1223 ui.status(_('largefiles: (no remote repo)\n'))
1229 return
1224 return
1230
1225
1231 toupload = set()
1226 toupload = set()
1232 lfhashes = set()
1227 lfhashes = set()
1233 def addfunc(fn, lfhash):
1228 def addfunc(fn, lfhash):
1234 toupload.add(fn)
1229 toupload.add(fn)
1235 lfhashes.add(lfhash)
1230 lfhashes.add(lfhash)
1236 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1231 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1237
1232
1238 if not toupload:
1233 if not toupload:
1239 # i18n: column positioning for "hg summary"
1234 # i18n: column positioning for "hg summary"
1240 ui.status(_('largefiles: (no files to upload)\n'))
1235 ui.status(_('largefiles: (no files to upload)\n'))
1241 else:
1236 else:
1242 # i18n: column positioning for "hg summary"
1237 # i18n: column positioning for "hg summary"
1243 ui.status(_('largefiles: %d entities for %d files to upload\n')
1238 ui.status(_('largefiles: %d entities for %d files to upload\n')
1244 % (len(lfhashes), len(toupload)))
1239 % (len(lfhashes), len(toupload)))
1245
1240
1246 @eh.wrapcommand('summary',
1241 @eh.wrapcommand('summary',
1247 opts=[('', 'large', None, _('display outgoing largefiles'))])
1242 opts=[('', 'large', None, _('display outgoing largefiles'))])
1248 def overridesummary(orig, ui, repo, *pats, **opts):
1243 def overridesummary(orig, ui, repo, *pats, **opts):
1249 try:
1244 try:
1250 repo.lfstatus = True
1245 repo.lfstatus = True
1251 orig(ui, repo, *pats, **opts)
1246 orig(ui, repo, *pats, **opts)
1252 finally:
1247 finally:
1253 repo.lfstatus = False
1248 repo.lfstatus = False
1254
1249
1255 @eh.wrapfunction(scmutil, 'addremove')
1250 @eh.wrapfunction(scmutil, 'addremove')
1256 def scmutiladdremove(orig, repo, matcher, prefix, opts=None):
1251 def scmutiladdremove(orig, repo, matcher, prefix, opts=None):
1257 if opts is None:
1252 if opts is None:
1258 opts = {}
1253 opts = {}
1259 if not lfutil.islfilesrepo(repo):
1254 if not lfutil.islfilesrepo(repo):
1260 return orig(repo, matcher, prefix, opts)
1255 return orig(repo, matcher, prefix, opts)
1261 # Get the list of missing largefiles so we can remove them
1256 # Get the list of missing largefiles so we can remove them
1262 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1257 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1263 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
1258 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
1264 subrepos=[], ignored=False, clean=False,
1259 subrepos=[], ignored=False, clean=False,
1265 unknown=False)
1260 unknown=False)
1266
1261
1267 # Call into the normal remove code, but the removing of the standin, we want
1262 # Call into the normal remove code, but the removing of the standin, we want
1268 # to have handled by original addremove. Monkey patching here makes sure
1263 # to have handled by original addremove. Monkey patching here makes sure
1269 # we don't remove the standin in the largefiles code, preventing a very
1264 # we don't remove the standin in the largefiles code, preventing a very
1270 # confused state later.
1265 # confused state later.
1271 if s.deleted:
1266 if s.deleted:
1272 m = copy.copy(matcher)
1267 m = copy.copy(matcher)
1273
1268
1274 # The m._files and m._map attributes are not changed to the deleted list
1269 # The m._files and m._map attributes are not changed to the deleted list
1275 # because that affects the m.exact() test, which in turn governs whether
1270 # because that affects the m.exact() test, which in turn governs whether
1276 # or not the file name is printed, and how. Simply limit the original
1271 # or not the file name is printed, and how. Simply limit the original
1277 # matches to those in the deleted status list.
1272 # matches to those in the deleted status list.
1278 matchfn = m.matchfn
1273 matchfn = m.matchfn
1279 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1274 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1280
1275
1281 removelargefiles(repo.ui, repo, True, m, opts.get('dry_run'),
1276 removelargefiles(repo.ui, repo, True, m, opts.get('dry_run'),
1282 **pycompat.strkwargs(opts))
1277 **pycompat.strkwargs(opts))
1283 # Call into the normal add code, and any files that *should* be added as
1278 # Call into the normal add code, and any files that *should* be added as
1284 # largefiles will be
1279 # largefiles will be
1285 added, bad = addlargefiles(repo.ui, repo, True, matcher,
1280 added, bad = addlargefiles(repo.ui, repo, True, matcher,
1286 **pycompat.strkwargs(opts))
1281 **pycompat.strkwargs(opts))
1287 # Now that we've handled largefiles, hand off to the original addremove
1282 # Now that we've handled largefiles, hand off to the original addremove
1288 # function to take care of the rest. Make sure it doesn't do anything with
1283 # function to take care of the rest. Make sure it doesn't do anything with
1289 # largefiles by passing a matcher that will ignore them.
1284 # largefiles by passing a matcher that will ignore them.
1290 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1285 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1291 return orig(repo, matcher, prefix, opts)
1286 return orig(repo, matcher, prefix, opts)
1292
1287
1293 # Calling purge with --all will cause the largefiles to be deleted.
1288 # Calling purge with --all will cause the largefiles to be deleted.
1294 # Override repo.status to prevent this from happening.
1289 # Override repo.status to prevent this from happening.
1295 @eh.wrapcommand('purge', extension='purge')
1290 @eh.wrapcommand('purge', extension='purge')
1296 def overridepurge(orig, ui, repo, *dirs, **opts):
1291 def overridepurge(orig, ui, repo, *dirs, **opts):
1297 # XXX Monkey patching a repoview will not work. The assigned attribute will
1292 # XXX Monkey patching a repoview will not work. The assigned attribute will
1298 # be set on the unfiltered repo, but we will only lookup attributes in the
1293 # be set on the unfiltered repo, but we will only lookup attributes in the
1299 # unfiltered repo if the lookup in the repoview object itself fails. As the
1294 # unfiltered repo if the lookup in the repoview object itself fails. As the
1300 # monkey patched method exists on the repoview class the lookup will not
1295 # monkey patched method exists on the repoview class the lookup will not
1301 # fail. As a result, the original version will shadow the monkey patched
1296 # fail. As a result, the original version will shadow the monkey patched
1302 # one, defeating the monkey patch.
1297 # one, defeating the monkey patch.
1303 #
1298 #
1304 # As a work around we use an unfiltered repo here. We should do something
1299 # As a work around we use an unfiltered repo here. We should do something
1305 # cleaner instead.
1300 # cleaner instead.
1306 repo = repo.unfiltered()
1301 repo = repo.unfiltered()
1307 oldstatus = repo.status
1302 oldstatus = repo.status
1308 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1303 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1309 clean=False, unknown=False, listsubrepos=False):
1304 clean=False, unknown=False, listsubrepos=False):
1310 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1305 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1311 listsubrepos)
1306 listsubrepos)
1312 lfdirstate = lfutil.openlfdirstate(ui, repo)
1307 lfdirstate = lfutil.openlfdirstate(ui, repo)
1313 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1308 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1314 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1309 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1315 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1310 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1316 unknown, ignored, r.clean)
1311 unknown, ignored, r.clean)
1317 repo.status = overridestatus
1312 repo.status = overridestatus
1318 orig(ui, repo, *dirs, **opts)
1313 orig(ui, repo, *dirs, **opts)
1319 repo.status = oldstatus
1314 repo.status = oldstatus
1320
1315
1321 @eh.wrapcommand('rollback')
1316 @eh.wrapcommand('rollback')
1322 def overriderollback(orig, ui, repo, **opts):
1317 def overriderollback(orig, ui, repo, **opts):
1323 with repo.wlock():
1318 with repo.wlock():
1324 before = repo.dirstate.parents()
1319 before = repo.dirstate.parents()
1325 orphans = set(f for f in repo.dirstate
1320 orphans = set(f for f in repo.dirstate
1326 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1321 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1327 result = orig(ui, repo, **opts)
1322 result = orig(ui, repo, **opts)
1328 after = repo.dirstate.parents()
1323 after = repo.dirstate.parents()
1329 if before == after:
1324 if before == after:
1330 return result # no need to restore standins
1325 return result # no need to restore standins
1331
1326
1332 pctx = repo['.']
1327 pctx = repo['.']
1333 for f in repo.dirstate:
1328 for f in repo.dirstate:
1334 if lfutil.isstandin(f):
1329 if lfutil.isstandin(f):
1335 orphans.discard(f)
1330 orphans.discard(f)
1336 if repo.dirstate[f] == 'r':
1331 if repo.dirstate[f] == 'r':
1337 repo.wvfs.unlinkpath(f, ignoremissing=True)
1332 repo.wvfs.unlinkpath(f, ignoremissing=True)
1338 elif f in pctx:
1333 elif f in pctx:
1339 fctx = pctx[f]
1334 fctx = pctx[f]
1340 repo.wwrite(f, fctx.data(), fctx.flags())
1335 repo.wwrite(f, fctx.data(), fctx.flags())
1341 else:
1336 else:
1342 # content of standin is not so important in 'a',
1337 # content of standin is not so important in 'a',
1343 # 'm' or 'n' (coming from the 2nd parent) cases
1338 # 'm' or 'n' (coming from the 2nd parent) cases
1344 lfutil.writestandin(repo, f, '', False)
1339 lfutil.writestandin(repo, f, '', False)
1345 for standin in orphans:
1340 for standin in orphans:
1346 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1341 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1347
1342
1348 lfdirstate = lfutil.openlfdirstate(ui, repo)
1343 lfdirstate = lfutil.openlfdirstate(ui, repo)
1349 orphans = set(lfdirstate)
1344 orphans = set(lfdirstate)
1350 lfiles = lfutil.listlfiles(repo)
1345 lfiles = lfutil.listlfiles(repo)
1351 for file in lfiles:
1346 for file in lfiles:
1352 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1347 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1353 orphans.discard(file)
1348 orphans.discard(file)
1354 for lfile in orphans:
1349 for lfile in orphans:
1355 lfdirstate.drop(lfile)
1350 lfdirstate.drop(lfile)
1356 lfdirstate.write()
1351 lfdirstate.write()
1357 return result
1352 return result
1358
1353
1359 @eh.wrapcommand('transplant', extension='transplant')
1354 @eh.wrapcommand('transplant', extension='transplant')
1360 def overridetransplant(orig, ui, repo, *revs, **opts):
1355 def overridetransplant(orig, ui, repo, *revs, **opts):
1361 resuming = opts.get(r'continue')
1356 resuming = opts.get(r'continue')
1362 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1357 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1363 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1358 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1364 try:
1359 try:
1365 result = orig(ui, repo, *revs, **opts)
1360 result = orig(ui, repo, *revs, **opts)
1366 finally:
1361 finally:
1367 repo._lfstatuswriters.pop()
1362 repo._lfstatuswriters.pop()
1368 repo._lfcommithooks.pop()
1363 repo._lfcommithooks.pop()
1369 return result
1364 return result
1370
1365
1371 @eh.wrapcommand('cat')
1366 @eh.wrapcommand('cat')
1372 def overridecat(orig, ui, repo, file1, *pats, **opts):
1367 def overridecat(orig, ui, repo, file1, *pats, **opts):
1373 opts = pycompat.byteskwargs(opts)
1368 opts = pycompat.byteskwargs(opts)
1374 ctx = scmutil.revsingle(repo, opts.get('rev'))
1369 ctx = scmutil.revsingle(repo, opts.get('rev'))
1375 err = 1
1370 err = 1
1376 notbad = set()
1371 notbad = set()
1377 m = scmutil.match(ctx, (file1,) + pats, opts)
1372 m = scmutil.match(ctx, (file1,) + pats, opts)
1378 origmatchfn = m.matchfn
1373 origmatchfn = m.matchfn
1379 def lfmatchfn(f):
1374 def lfmatchfn(f):
1380 if origmatchfn(f):
1375 if origmatchfn(f):
1381 return True
1376 return True
1382 lf = lfutil.splitstandin(f)
1377 lf = lfutil.splitstandin(f)
1383 if lf is None:
1378 if lf is None:
1384 return False
1379 return False
1385 notbad.add(lf)
1380 notbad.add(lf)
1386 return origmatchfn(lf)
1381 return origmatchfn(lf)
1387 m.matchfn = lfmatchfn
1382 m.matchfn = lfmatchfn
1388 origbadfn = m.bad
1383 origbadfn = m.bad
1389 def lfbadfn(f, msg):
1384 def lfbadfn(f, msg):
1390 if not f in notbad:
1385 if not f in notbad:
1391 origbadfn(f, msg)
1386 origbadfn(f, msg)
1392 m.bad = lfbadfn
1387 m.bad = lfbadfn
1393
1388
1394 origvisitdirfn = m.visitdir
1389 origvisitdirfn = m.visitdir
1395 def lfvisitdirfn(dir):
1390 def lfvisitdirfn(dir):
1396 if dir == lfutil.shortname:
1391 if dir == lfutil.shortname:
1397 return True
1392 return True
1398 ret = origvisitdirfn(dir)
1393 ret = origvisitdirfn(dir)
1399 if ret:
1394 if ret:
1400 return ret
1395 return ret
1401 lf = lfutil.splitstandin(dir)
1396 lf = lfutil.splitstandin(dir)
1402 if lf is None:
1397 if lf is None:
1403 return False
1398 return False
1404 return origvisitdirfn(lf)
1399 return origvisitdirfn(lf)
1405 m.visitdir = lfvisitdirfn
1400 m.visitdir = lfvisitdirfn
1406
1401
1407 for f in ctx.walk(m):
1402 for f in ctx.walk(m):
1408 with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp:
1403 with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp:
1409 lf = lfutil.splitstandin(f)
1404 lf = lfutil.splitstandin(f)
1410 if lf is None or origmatchfn(f):
1405 if lf is None or origmatchfn(f):
1411 # duplicating unreachable code from commands.cat
1406 # duplicating unreachable code from commands.cat
1412 data = ctx[f].data()
1407 data = ctx[f].data()
1413 if opts.get('decode'):
1408 if opts.get('decode'):
1414 data = repo.wwritedata(f, data)
1409 data = repo.wwritedata(f, data)
1415 fp.write(data)
1410 fp.write(data)
1416 else:
1411 else:
1417 hash = lfutil.readasstandin(ctx[f])
1412 hash = lfutil.readasstandin(ctx[f])
1418 if not lfutil.inusercache(repo.ui, hash):
1413 if not lfutil.inusercache(repo.ui, hash):
1419 store = storefactory.openstore(repo)
1414 store = storefactory.openstore(repo)
1420 success, missing = store.get([(lf, hash)])
1415 success, missing = store.get([(lf, hash)])
1421 if len(success) != 1:
1416 if len(success) != 1:
1422 raise error.Abort(
1417 raise error.Abort(
1423 _('largefile %s is not in cache and could not be '
1418 _('largefile %s is not in cache and could not be '
1424 'downloaded') % lf)
1419 'downloaded') % lf)
1425 path = lfutil.usercachepath(repo.ui, hash)
1420 path = lfutil.usercachepath(repo.ui, hash)
1426 with open(path, "rb") as fpin:
1421 with open(path, "rb") as fpin:
1427 for chunk in util.filechunkiter(fpin):
1422 for chunk in util.filechunkiter(fpin):
1428 fp.write(chunk)
1423 fp.write(chunk)
1429 err = 0
1424 err = 0
1430 return err
1425 return err
1431
1426
1432 @eh.wrapfunction(merge, 'update')
1427 @eh.wrapfunction(merge, 'update')
1433 def mergeupdate(orig, repo, node, branchmerge, force,
1428 def mergeupdate(orig, repo, node, branchmerge, force,
1434 *args, **kwargs):
1429 *args, **kwargs):
1435 matcher = kwargs.get(r'matcher', None)
1430 matcher = kwargs.get(r'matcher', None)
1436 # note if this is a partial update
1431 # note if this is a partial update
1437 partial = matcher and not matcher.always()
1432 partial = matcher and not matcher.always()
1438 with repo.wlock():
1433 with repo.wlock():
1439 # branch | | |
1434 # branch | | |
1440 # merge | force | partial | action
1435 # merge | force | partial | action
1441 # -------+-------+---------+--------------
1436 # -------+-------+---------+--------------
1442 # x | x | x | linear-merge
1437 # x | x | x | linear-merge
1443 # o | x | x | branch-merge
1438 # o | x | x | branch-merge
1444 # x | o | x | overwrite (as clean update)
1439 # x | o | x | overwrite (as clean update)
1445 # o | o | x | force-branch-merge (*1)
1440 # o | o | x | force-branch-merge (*1)
1446 # x | x | o | (*)
1441 # x | x | o | (*)
1447 # o | x | o | (*)
1442 # o | x | o | (*)
1448 # x | o | o | overwrite (as revert)
1443 # x | o | o | overwrite (as revert)
1449 # o | o | o | (*)
1444 # o | o | o | (*)
1450 #
1445 #
1451 # (*) don't care
1446 # (*) don't care
1452 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1447 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1453
1448
1454 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1449 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1455 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1450 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1456 repo.getcwd()),
1451 repo.getcwd()),
1457 subrepos=[], ignored=False,
1452 subrepos=[], ignored=False,
1458 clean=True, unknown=False)
1453 clean=True, unknown=False)
1459 oldclean = set(s.clean)
1454 oldclean = set(s.clean)
1460 pctx = repo['.']
1455 pctx = repo['.']
1461 dctx = repo[node]
1456 dctx = repo[node]
1462 for lfile in unsure + s.modified:
1457 for lfile in unsure + s.modified:
1463 lfileabs = repo.wvfs.join(lfile)
1458 lfileabs = repo.wvfs.join(lfile)
1464 if not repo.wvfs.exists(lfileabs):
1459 if not repo.wvfs.exists(lfileabs):
1465 continue
1460 continue
1466 lfhash = lfutil.hashfile(lfileabs)
1461 lfhash = lfutil.hashfile(lfileabs)
1467 standin = lfutil.standin(lfile)
1462 standin = lfutil.standin(lfile)
1468 lfutil.writestandin(repo, standin, lfhash,
1463 lfutil.writestandin(repo, standin, lfhash,
1469 lfutil.getexecutable(lfileabs))
1464 lfutil.getexecutable(lfileabs))
1470 if (standin in pctx and
1465 if (standin in pctx and
1471 lfhash == lfutil.readasstandin(pctx[standin])):
1466 lfhash == lfutil.readasstandin(pctx[standin])):
1472 oldclean.add(lfile)
1467 oldclean.add(lfile)
1473 for lfile in s.added:
1468 for lfile in s.added:
1474 fstandin = lfutil.standin(lfile)
1469 fstandin = lfutil.standin(lfile)
1475 if fstandin not in dctx:
1470 if fstandin not in dctx:
1476 # in this case, content of standin file is meaningless
1471 # in this case, content of standin file is meaningless
1477 # (in dctx, lfile is unknown, or normal file)
1472 # (in dctx, lfile is unknown, or normal file)
1478 continue
1473 continue
1479 lfutil.updatestandin(repo, lfile, fstandin)
1474 lfutil.updatestandin(repo, lfile, fstandin)
1480 # mark all clean largefiles as dirty, just in case the update gets
1475 # mark all clean largefiles as dirty, just in case the update gets
1481 # interrupted before largefiles and lfdirstate are synchronized
1476 # interrupted before largefiles and lfdirstate are synchronized
1482 for lfile in oldclean:
1477 for lfile in oldclean:
1483 lfdirstate.normallookup(lfile)
1478 lfdirstate.normallookup(lfile)
1484 lfdirstate.write()
1479 lfdirstate.write()
1485
1480
1486 oldstandins = lfutil.getstandinsstate(repo)
1481 oldstandins = lfutil.getstandinsstate(repo)
1487 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1482 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1488 # good candidate for in-memory merge (large files, custom dirstate,
1483 # good candidate for in-memory merge (large files, custom dirstate,
1489 # matcher usage).
1484 # matcher usage).
1490 kwargs[r'wc'] = repo[None]
1485 kwargs[r'wc'] = repo[None]
1491 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1486 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1492
1487
1493 newstandins = lfutil.getstandinsstate(repo)
1488 newstandins = lfutil.getstandinsstate(repo)
1494 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1489 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1495
1490
1496 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1491 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1497 # all the ones that didn't change as clean
1492 # all the ones that didn't change as clean
1498 for lfile in oldclean.difference(filelist):
1493 for lfile in oldclean.difference(filelist):
1499 lfdirstate.normal(lfile)
1494 lfdirstate.normal(lfile)
1500 lfdirstate.write()
1495 lfdirstate.write()
1501
1496
1502 if branchmerge or force or partial:
1497 if branchmerge or force or partial:
1503 filelist.extend(s.deleted + s.removed)
1498 filelist.extend(s.deleted + s.removed)
1504
1499
1505 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1500 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1506 normallookup=partial)
1501 normallookup=partial)
1507
1502
1508 return result
1503 return result
1509
1504
1510 @eh.wrapfunction(scmutil, 'marktouched')
1505 @eh.wrapfunction(scmutil, 'marktouched')
1511 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1506 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1512 result = orig(repo, files, *args, **kwargs)
1507 result = orig(repo, files, *args, **kwargs)
1513
1508
1514 filelist = []
1509 filelist = []
1515 for f in files:
1510 for f in files:
1516 lf = lfutil.splitstandin(f)
1511 lf = lfutil.splitstandin(f)
1517 if lf is not None:
1512 if lf is not None:
1518 filelist.append(lf)
1513 filelist.append(lf)
1519 if filelist:
1514 if filelist:
1520 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1515 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1521 printmessage=False, normallookup=True)
1516 printmessage=False, normallookup=True)
1522
1517
1523 return result
1518 return result
1524
1519
1525 @eh.wrapfunction(upgrade, 'preservedrequirements')
1520 @eh.wrapfunction(upgrade, 'preservedrequirements')
1526 @eh.wrapfunction(upgrade, 'supporteddestrequirements')
1521 @eh.wrapfunction(upgrade, 'supporteddestrequirements')
1527 def upgraderequirements(orig, repo):
1522 def upgraderequirements(orig, repo):
1528 reqs = orig(repo)
1523 reqs = orig(repo)
1529 if 'largefiles' in repo.requirements:
1524 if 'largefiles' in repo.requirements:
1530 reqs.add('largefiles')
1525 reqs.add('largefiles')
1531 return reqs
1526 return reqs
1532
1527
1533 _lfscheme = 'largefile://'
1528 _lfscheme = 'largefile://'
1534
1529
1535 @eh.wrapfunction(urlmod, 'open')
1530 @eh.wrapfunction(urlmod, 'open')
1536 def openlargefile(orig, ui, url_, data=None):
1531 def openlargefile(orig, ui, url_, data=None):
1537 if url_.startswith(_lfscheme):
1532 if url_.startswith(_lfscheme):
1538 if data:
1533 if data:
1539 msg = "cannot use data on a 'largefile://' url"
1534 msg = "cannot use data on a 'largefile://' url"
1540 raise error.ProgrammingError(msg)
1535 raise error.ProgrammingError(msg)
1541 lfid = url_[len(_lfscheme):]
1536 lfid = url_[len(_lfscheme):]
1542 return storefactory.getlfile(ui, lfid)
1537 return storefactory.getlfile(ui, lfid)
1543 else:
1538 else:
1544 return orig(ui, url_, data=data)
1539 return orig(ui, url_, data=data)
General Comments 0
You need to be logged in to leave comments. Login now