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