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