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