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