##// END OF EJS Templates
copyfile: preserve stat info (mtime, etc.) when doing copies/renames...
Kyle Lippincott -
r37106:08890706 default
parent child Browse files
Show More
@@ -1,1493 +1,1493 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import copy
12 import copy
13 import os
13 import os
14
14
15 from mercurial.i18n import _
15 from mercurial.i18n import _
16
16
17 from mercurial import (
17 from mercurial import (
18 archival,
18 archival,
19 cmdutil,
19 cmdutil,
20 error,
20 error,
21 hg,
21 hg,
22 logcmdutil,
22 logcmdutil,
23 match as matchmod,
23 match as matchmod,
24 pathutil,
24 pathutil,
25 pycompat,
25 pycompat,
26 registrar,
26 registrar,
27 scmutil,
27 scmutil,
28 smartset,
28 smartset,
29 util,
29 util,
30 )
30 )
31
31
32 from . import (
32 from . import (
33 lfcommands,
33 lfcommands,
34 lfutil,
34 lfutil,
35 storefactory,
35 storefactory,
36 )
36 )
37
37
38 # -- Utility functions: commonly/repeatedly needed functionality ---------------
38 # -- Utility functions: commonly/repeatedly needed functionality ---------------
39
39
40 def composelargefilematcher(match, manifest):
40 def composelargefilematcher(match, manifest):
41 '''create a matcher that matches only the largefiles in the original
41 '''create a matcher that matches only the largefiles in the original
42 matcher'''
42 matcher'''
43 m = copy.copy(match)
43 m = copy.copy(match)
44 lfile = lambda f: lfutil.standin(f) in manifest
44 lfile = lambda f: lfutil.standin(f) in manifest
45 m._files = [lf for lf in m._files if lfile(lf)]
45 m._files = [lf for lf in m._files if lfile(lf)]
46 m._fileset = set(m._files)
46 m._fileset = set(m._files)
47 m.always = lambda: False
47 m.always = lambda: False
48 origmatchfn = m.matchfn
48 origmatchfn = m.matchfn
49 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
49 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
50 return m
50 return m
51
51
52 def composenormalfilematcher(match, manifest, exclude=None):
52 def composenormalfilematcher(match, manifest, exclude=None):
53 excluded = set()
53 excluded = set()
54 if exclude is not None:
54 if exclude is not None:
55 excluded.update(exclude)
55 excluded.update(exclude)
56
56
57 m = copy.copy(match)
57 m = copy.copy(match)
58 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
58 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
59 manifest or f in excluded)
59 manifest or f in excluded)
60 m._files = [lf for lf in m._files if notlfile(lf)]
60 m._files = [lf for lf in m._files if notlfile(lf)]
61 m._fileset = set(m._files)
61 m._fileset = set(m._files)
62 m.always = lambda: False
62 m.always = lambda: False
63 origmatchfn = m.matchfn
63 origmatchfn = m.matchfn
64 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
64 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
65 return m
65 return m
66
66
67 def installnormalfilesmatchfn(manifest):
67 def installnormalfilesmatchfn(manifest):
68 '''installmatchfn with a matchfn that ignores all largefiles'''
68 '''installmatchfn with a matchfn that ignores all largefiles'''
69 def overridematch(ctx, pats=(), opts=None, globbed=False,
69 def overridematch(ctx, pats=(), opts=None, globbed=False,
70 default='relpath', badfn=None):
70 default='relpath', badfn=None):
71 if opts is None:
71 if opts is None:
72 opts = {}
72 opts = {}
73 match = oldmatch(ctx, pats, opts, globbed, default, badfn=badfn)
73 match = oldmatch(ctx, pats, opts, globbed, default, badfn=badfn)
74 return composenormalfilematcher(match, manifest)
74 return composenormalfilematcher(match, manifest)
75 oldmatch = installmatchfn(overridematch)
75 oldmatch = installmatchfn(overridematch)
76
76
77 def installmatchfn(f):
77 def installmatchfn(f):
78 '''monkey patch the scmutil module with a custom match function.
78 '''monkey patch the scmutil module with a custom match function.
79 Warning: it is monkey patching the _module_ on runtime! Not thread safe!'''
79 Warning: it is monkey patching the _module_ on runtime! Not thread safe!'''
80 oldmatch = scmutil.match
80 oldmatch = scmutil.match
81 setattr(f, 'oldmatch', oldmatch)
81 setattr(f, 'oldmatch', oldmatch)
82 scmutil.match = f
82 scmutil.match = f
83 return oldmatch
83 return oldmatch
84
84
85 def restorematchfn():
85 def restorematchfn():
86 '''restores scmutil.match to what it was before installmatchfn
86 '''restores scmutil.match to what it was before installmatchfn
87 was called. no-op if scmutil.match is its original function.
87 was called. no-op if scmutil.match is its original function.
88
88
89 Note that n calls to installmatchfn will require n calls to
89 Note that n calls to installmatchfn will require n calls to
90 restore the original matchfn.'''
90 restore the original matchfn.'''
91 scmutil.match = getattr(scmutil.match, 'oldmatch')
91 scmutil.match = getattr(scmutil.match, 'oldmatch')
92
92
93 def installmatchandpatsfn(f):
93 def installmatchandpatsfn(f):
94 oldmatchandpats = scmutil.matchandpats
94 oldmatchandpats = scmutil.matchandpats
95 setattr(f, 'oldmatchandpats', oldmatchandpats)
95 setattr(f, 'oldmatchandpats', oldmatchandpats)
96 scmutil.matchandpats = f
96 scmutil.matchandpats = f
97 return oldmatchandpats
97 return oldmatchandpats
98
98
99 def restorematchandpatsfn():
99 def restorematchandpatsfn():
100 '''restores scmutil.matchandpats to what it was before
100 '''restores scmutil.matchandpats to what it was before
101 installmatchandpatsfn was called. No-op if scmutil.matchandpats
101 installmatchandpatsfn was called. No-op if scmutil.matchandpats
102 is its original function.
102 is its original function.
103
103
104 Note that n calls to installmatchandpatsfn will require n calls
104 Note that n calls to installmatchandpatsfn will require n calls
105 to restore the original matchfn.'''
105 to restore the original matchfn.'''
106 scmutil.matchandpats = getattr(scmutil.matchandpats, 'oldmatchandpats',
106 scmutil.matchandpats = getattr(scmutil.matchandpats, 'oldmatchandpats',
107 scmutil.matchandpats)
107 scmutil.matchandpats)
108
108
109 def addlargefiles(ui, repo, isaddremove, matcher, **opts):
109 def addlargefiles(ui, repo, isaddremove, matcher, **opts):
110 large = opts.get(r'large')
110 large = opts.get(r'large')
111 lfsize = lfutil.getminsize(
111 lfsize = lfutil.getminsize(
112 ui, lfutil.islfilesrepo(repo), opts.get(r'lfsize'))
112 ui, lfutil.islfilesrepo(repo), opts.get(r'lfsize'))
113
113
114 lfmatcher = None
114 lfmatcher = None
115 if lfutil.islfilesrepo(repo):
115 if lfutil.islfilesrepo(repo):
116 lfpats = ui.configlist(lfutil.longname, 'patterns')
116 lfpats = ui.configlist(lfutil.longname, 'patterns')
117 if lfpats:
117 if lfpats:
118 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
118 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
119
119
120 lfnames = []
120 lfnames = []
121 m = matcher
121 m = matcher
122
122
123 wctx = repo[None]
123 wctx = repo[None]
124 for f in wctx.walk(matchmod.badmatch(m, lambda x, y: None)):
124 for f in wctx.walk(matchmod.badmatch(m, lambda x, y: None)):
125 exact = m.exact(f)
125 exact = m.exact(f)
126 lfile = lfutil.standin(f) in wctx
126 lfile = lfutil.standin(f) in wctx
127 nfile = f in wctx
127 nfile = f in wctx
128 exists = lfile or nfile
128 exists = lfile or nfile
129
129
130 # addremove in core gets fancy with the name, add doesn't
130 # addremove in core gets fancy with the name, add doesn't
131 if isaddremove:
131 if isaddremove:
132 name = m.uipath(f)
132 name = m.uipath(f)
133 else:
133 else:
134 name = m.rel(f)
134 name = m.rel(f)
135
135
136 # Don't warn the user when they attempt to add a normal tracked file.
136 # Don't warn the user when they attempt to add a normal tracked file.
137 # The normal add code will do that for us.
137 # The normal add code will do that for us.
138 if exact and exists:
138 if exact and exists:
139 if lfile:
139 if lfile:
140 ui.warn(_('%s already a largefile\n') % name)
140 ui.warn(_('%s already a largefile\n') % name)
141 continue
141 continue
142
142
143 if (exact or not exists) and not lfutil.isstandin(f):
143 if (exact or not exists) and not lfutil.isstandin(f):
144 # In case the file was removed previously, but not committed
144 # In case the file was removed previously, but not committed
145 # (issue3507)
145 # (issue3507)
146 if not repo.wvfs.exists(f):
146 if not repo.wvfs.exists(f):
147 continue
147 continue
148
148
149 abovemin = (lfsize and
149 abovemin = (lfsize and
150 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
150 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
151 if large or abovemin or (lfmatcher and lfmatcher(f)):
151 if large or abovemin or (lfmatcher and lfmatcher(f)):
152 lfnames.append(f)
152 lfnames.append(f)
153 if ui.verbose or not exact:
153 if ui.verbose or not exact:
154 ui.status(_('adding %s as a largefile\n') % name)
154 ui.status(_('adding %s as a largefile\n') % name)
155
155
156 bad = []
156 bad = []
157
157
158 # Need to lock, otherwise there could be a race condition between
158 # Need to lock, otherwise there could be a race condition between
159 # when standins are created and added to the repo.
159 # when standins are created and added to the repo.
160 with repo.wlock():
160 with repo.wlock():
161 if not opts.get(r'dry_run'):
161 if not opts.get(r'dry_run'):
162 standins = []
162 standins = []
163 lfdirstate = lfutil.openlfdirstate(ui, repo)
163 lfdirstate = lfutil.openlfdirstate(ui, repo)
164 for f in lfnames:
164 for f in lfnames:
165 standinname = lfutil.standin(f)
165 standinname = lfutil.standin(f)
166 lfutil.writestandin(repo, standinname, hash='',
166 lfutil.writestandin(repo, standinname, hash='',
167 executable=lfutil.getexecutable(repo.wjoin(f)))
167 executable=lfutil.getexecutable(repo.wjoin(f)))
168 standins.append(standinname)
168 standins.append(standinname)
169 if lfdirstate[f] == 'r':
169 if lfdirstate[f] == 'r':
170 lfdirstate.normallookup(f)
170 lfdirstate.normallookup(f)
171 else:
171 else:
172 lfdirstate.add(f)
172 lfdirstate.add(f)
173 lfdirstate.write()
173 lfdirstate.write()
174 bad += [lfutil.splitstandin(f)
174 bad += [lfutil.splitstandin(f)
175 for f in repo[None].add(standins)
175 for f in repo[None].add(standins)
176 if f in m.files()]
176 if f in m.files()]
177
177
178 added = [f for f in lfnames if f not in bad]
178 added = [f for f in lfnames if f not in bad]
179 return added, bad
179 return added, bad
180
180
181 def removelargefiles(ui, repo, isaddremove, matcher, **opts):
181 def removelargefiles(ui, repo, isaddremove, matcher, **opts):
182 after = opts.get(r'after')
182 after = opts.get(r'after')
183 m = composelargefilematcher(matcher, repo[None].manifest())
183 m = composelargefilematcher(matcher, repo[None].manifest())
184 try:
184 try:
185 repo.lfstatus = True
185 repo.lfstatus = True
186 s = repo.status(match=m, clean=not isaddremove)
186 s = repo.status(match=m, clean=not isaddremove)
187 finally:
187 finally:
188 repo.lfstatus = False
188 repo.lfstatus = False
189 manifest = repo[None].manifest()
189 manifest = repo[None].manifest()
190 modified, added, deleted, clean = [[f for f in list
190 modified, added, deleted, clean = [[f for f in list
191 if lfutil.standin(f) in manifest]
191 if lfutil.standin(f) in manifest]
192 for list in (s.modified, s.added,
192 for list in (s.modified, s.added,
193 s.deleted, s.clean)]
193 s.deleted, s.clean)]
194
194
195 def warn(files, msg):
195 def warn(files, msg):
196 for f in files:
196 for f in files:
197 ui.warn(msg % m.rel(f))
197 ui.warn(msg % m.rel(f))
198 return int(len(files) > 0)
198 return int(len(files) > 0)
199
199
200 result = 0
200 result = 0
201
201
202 if after:
202 if after:
203 remove = deleted
203 remove = deleted
204 result = warn(modified + added + clean,
204 result = warn(modified + added + clean,
205 _('not removing %s: file still exists\n'))
205 _('not removing %s: file still exists\n'))
206 else:
206 else:
207 remove = deleted + clean
207 remove = deleted + clean
208 result = warn(modified, _('not removing %s: file is modified (use -f'
208 result = warn(modified, _('not removing %s: file is modified (use -f'
209 ' to force removal)\n'))
209 ' to force removal)\n'))
210 result = warn(added, _('not removing %s: file has been marked for add'
210 result = warn(added, _('not removing %s: file has been marked for add'
211 ' (use forget to undo)\n')) or result
211 ' (use forget to undo)\n')) or result
212
212
213 # Need to lock because standin files are deleted then removed from the
213 # Need to lock because standin files are deleted then removed from the
214 # repository and we could race in-between.
214 # repository and we could race in-between.
215 with repo.wlock():
215 with repo.wlock():
216 lfdirstate = lfutil.openlfdirstate(ui, repo)
216 lfdirstate = lfutil.openlfdirstate(ui, repo)
217 for f in sorted(remove):
217 for f in sorted(remove):
218 if ui.verbose or not m.exact(f):
218 if ui.verbose or not m.exact(f):
219 # addremove in core gets fancy with the name, remove doesn't
219 # addremove in core gets fancy with the name, remove doesn't
220 if isaddremove:
220 if isaddremove:
221 name = m.uipath(f)
221 name = m.uipath(f)
222 else:
222 else:
223 name = m.rel(f)
223 name = m.rel(f)
224 ui.status(_('removing %s\n') % name)
224 ui.status(_('removing %s\n') % name)
225
225
226 if not opts.get(r'dry_run'):
226 if not opts.get(r'dry_run'):
227 if not after:
227 if not after:
228 repo.wvfs.unlinkpath(f, ignoremissing=True)
228 repo.wvfs.unlinkpath(f, ignoremissing=True)
229
229
230 if opts.get(r'dry_run'):
230 if opts.get(r'dry_run'):
231 return result
231 return result
232
232
233 remove = [lfutil.standin(f) for f in remove]
233 remove = [lfutil.standin(f) for f in remove]
234 # If this is being called by addremove, let the original addremove
234 # If this is being called by addremove, let the original addremove
235 # function handle this.
235 # function handle this.
236 if not isaddremove:
236 if not isaddremove:
237 for f in remove:
237 for f in remove:
238 repo.wvfs.unlinkpath(f, ignoremissing=True)
238 repo.wvfs.unlinkpath(f, ignoremissing=True)
239 repo[None].forget(remove)
239 repo[None].forget(remove)
240
240
241 for f in remove:
241 for f in remove:
242 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
242 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
243 False)
243 False)
244
244
245 lfdirstate.write()
245 lfdirstate.write()
246
246
247 return result
247 return result
248
248
249 # For overriding mercurial.hgweb.webcommands so that largefiles will
249 # For overriding mercurial.hgweb.webcommands so that largefiles will
250 # appear at their right place in the manifests.
250 # appear at their right place in the manifests.
251 def decodepath(orig, path):
251 def decodepath(orig, path):
252 return lfutil.splitstandin(path) or path
252 return lfutil.splitstandin(path) or path
253
253
254 # -- Wrappers: modify existing commands --------------------------------
254 # -- Wrappers: modify existing commands --------------------------------
255
255
256 def overrideadd(orig, ui, repo, *pats, **opts):
256 def overrideadd(orig, ui, repo, *pats, **opts):
257 if opts.get(r'normal') and opts.get(r'large'):
257 if opts.get(r'normal') and opts.get(r'large'):
258 raise error.Abort(_('--normal cannot be used with --large'))
258 raise error.Abort(_('--normal cannot be used with --large'))
259 return orig(ui, repo, *pats, **opts)
259 return orig(ui, repo, *pats, **opts)
260
260
261 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts):
261 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts):
262 # The --normal flag short circuits this override
262 # The --normal flag short circuits this override
263 if opts.get(r'normal'):
263 if opts.get(r'normal'):
264 return orig(ui, repo, matcher, prefix, explicitonly, **opts)
264 return orig(ui, repo, matcher, prefix, explicitonly, **opts)
265
265
266 ladded, lbad = addlargefiles(ui, repo, False, matcher, **opts)
266 ladded, lbad = addlargefiles(ui, repo, False, matcher, **opts)
267 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
267 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
268 ladded)
268 ladded)
269 bad = orig(ui, repo, normalmatcher, prefix, explicitonly, **opts)
269 bad = orig(ui, repo, normalmatcher, prefix, explicitonly, **opts)
270
270
271 bad.extend(f for f in lbad)
271 bad.extend(f for f in lbad)
272 return bad
272 return bad
273
273
274 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos):
274 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos):
275 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
275 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
276 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos)
276 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos)
277 return removelargefiles(ui, repo, False, matcher, after=after,
277 return removelargefiles(ui, repo, False, matcher, after=after,
278 force=force) or result
278 force=force) or result
279
279
280 def overridestatusfn(orig, repo, rev2, **opts):
280 def overridestatusfn(orig, repo, rev2, **opts):
281 try:
281 try:
282 repo._repo.lfstatus = True
282 repo._repo.lfstatus = True
283 return orig(repo, rev2, **opts)
283 return orig(repo, rev2, **opts)
284 finally:
284 finally:
285 repo._repo.lfstatus = False
285 repo._repo.lfstatus = False
286
286
287 def overridestatus(orig, ui, repo, *pats, **opts):
287 def overridestatus(orig, ui, repo, *pats, **opts):
288 try:
288 try:
289 repo.lfstatus = True
289 repo.lfstatus = True
290 return orig(ui, repo, *pats, **opts)
290 return orig(ui, repo, *pats, **opts)
291 finally:
291 finally:
292 repo.lfstatus = False
292 repo.lfstatus = False
293
293
294 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
294 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
295 try:
295 try:
296 repo._repo.lfstatus = True
296 repo._repo.lfstatus = True
297 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
297 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
298 finally:
298 finally:
299 repo._repo.lfstatus = False
299 repo._repo.lfstatus = False
300
300
301 def overridelog(orig, ui, repo, *pats, **opts):
301 def overridelog(orig, ui, repo, *pats, **opts):
302 def overridematchandpats(ctx, pats=(), opts=None, globbed=False,
302 def overridematchandpats(ctx, pats=(), opts=None, globbed=False,
303 default='relpath', badfn=None):
303 default='relpath', badfn=None):
304 """Matcher that merges root directory with .hglf, suitable for log.
304 """Matcher that merges root directory with .hglf, suitable for log.
305 It is still possible to match .hglf directly.
305 It is still possible to match .hglf directly.
306 For any listed files run log on the standin too.
306 For any listed files run log on the standin too.
307 matchfn tries both the given filename and with .hglf stripped.
307 matchfn tries both the given filename and with .hglf stripped.
308 """
308 """
309 if opts is None:
309 if opts is None:
310 opts = {}
310 opts = {}
311 matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default,
311 matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default,
312 badfn=badfn)
312 badfn=badfn)
313 m, p = copy.copy(matchandpats)
313 m, p = copy.copy(matchandpats)
314
314
315 if m.always():
315 if m.always():
316 # We want to match everything anyway, so there's no benefit trying
316 # We want to match everything anyway, so there's no benefit trying
317 # to add standins.
317 # to add standins.
318 return matchandpats
318 return matchandpats
319
319
320 pats = set(p)
320 pats = set(p)
321
321
322 def fixpats(pat, tostandin=lfutil.standin):
322 def fixpats(pat, tostandin=lfutil.standin):
323 if pat.startswith('set:'):
323 if pat.startswith('set:'):
324 return pat
324 return pat
325
325
326 kindpat = matchmod._patsplit(pat, None)
326 kindpat = matchmod._patsplit(pat, None)
327
327
328 if kindpat[0] is not None:
328 if kindpat[0] is not None:
329 return kindpat[0] + ':' + tostandin(kindpat[1])
329 return kindpat[0] + ':' + tostandin(kindpat[1])
330 return tostandin(kindpat[1])
330 return tostandin(kindpat[1])
331
331
332 if m._cwd:
332 if m._cwd:
333 hglf = lfutil.shortname
333 hglf = lfutil.shortname
334 back = util.pconvert(m.rel(hglf)[:-len(hglf)])
334 back = util.pconvert(m.rel(hglf)[:-len(hglf)])
335
335
336 def tostandin(f):
336 def tostandin(f):
337 # The file may already be a standin, so truncate the back
337 # The file may already be a standin, so truncate the back
338 # prefix and test before mangling it. This avoids turning
338 # prefix and test before mangling it. This avoids turning
339 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
339 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
340 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
340 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
341 return f
341 return f
342
342
343 # An absolute path is from outside the repo, so truncate the
343 # An absolute path is from outside the repo, so truncate the
344 # path to the root before building the standin. Otherwise cwd
344 # path to the root before building the standin. Otherwise cwd
345 # is somewhere in the repo, relative to root, and needs to be
345 # is somewhere in the repo, relative to root, and needs to be
346 # prepended before building the standin.
346 # prepended before building the standin.
347 if os.path.isabs(m._cwd):
347 if os.path.isabs(m._cwd):
348 f = f[len(back):]
348 f = f[len(back):]
349 else:
349 else:
350 f = m._cwd + '/' + f
350 f = m._cwd + '/' + f
351 return back + lfutil.standin(f)
351 return back + lfutil.standin(f)
352 else:
352 else:
353 def tostandin(f):
353 def tostandin(f):
354 if lfutil.isstandin(f):
354 if lfutil.isstandin(f):
355 return f
355 return f
356 return lfutil.standin(f)
356 return lfutil.standin(f)
357 pats.update(fixpats(f, tostandin) for f in p)
357 pats.update(fixpats(f, tostandin) for f in p)
358
358
359 for i in range(0, len(m._files)):
359 for i in range(0, len(m._files)):
360 # Don't add '.hglf' to m.files, since that is already covered by '.'
360 # Don't add '.hglf' to m.files, since that is already covered by '.'
361 if m._files[i] == '.':
361 if m._files[i] == '.':
362 continue
362 continue
363 standin = lfutil.standin(m._files[i])
363 standin = lfutil.standin(m._files[i])
364 # If the "standin" is a directory, append instead of replace to
364 # If the "standin" is a directory, append instead of replace to
365 # support naming a directory on the command line with only
365 # support naming a directory on the command line with only
366 # largefiles. The original directory is kept to support normal
366 # largefiles. The original directory is kept to support normal
367 # files.
367 # files.
368 if standin in ctx:
368 if standin in ctx:
369 m._files[i] = standin
369 m._files[i] = standin
370 elif m._files[i] not in ctx and repo.wvfs.isdir(standin):
370 elif m._files[i] not in ctx and repo.wvfs.isdir(standin):
371 m._files.append(standin)
371 m._files.append(standin)
372
372
373 m._fileset = set(m._files)
373 m._fileset = set(m._files)
374 m.always = lambda: False
374 m.always = lambda: False
375 origmatchfn = m.matchfn
375 origmatchfn = m.matchfn
376 def lfmatchfn(f):
376 def lfmatchfn(f):
377 lf = lfutil.splitstandin(f)
377 lf = lfutil.splitstandin(f)
378 if lf is not None and origmatchfn(lf):
378 if lf is not None and origmatchfn(lf):
379 return True
379 return True
380 r = origmatchfn(f)
380 r = origmatchfn(f)
381 return r
381 return r
382 m.matchfn = lfmatchfn
382 m.matchfn = lfmatchfn
383
383
384 ui.debug('updated patterns: %s\n' % ', '.join(sorted(pats)))
384 ui.debug('updated patterns: %s\n' % ', '.join(sorted(pats)))
385 return m, pats
385 return m, pats
386
386
387 # For hg log --patch, the match object is used in two different senses:
387 # For hg log --patch, the match object is used in two different senses:
388 # (1) to determine what revisions should be printed out, and
388 # (1) to determine what revisions should be printed out, and
389 # (2) to determine what files to print out diffs for.
389 # (2) to determine what files to print out diffs for.
390 # The magic matchandpats override should be used for case (1) but not for
390 # The magic matchandpats override should be used for case (1) but not for
391 # case (2).
391 # case (2).
392 def overridemakefilematcher(repo, pats, opts, badfn=None):
392 def overridemakefilematcher(repo, pats, opts, badfn=None):
393 wctx = repo[None]
393 wctx = repo[None]
394 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
394 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
395 return lambda ctx: match
395 return lambda ctx: match
396
396
397 oldmatchandpats = installmatchandpatsfn(overridematchandpats)
397 oldmatchandpats = installmatchandpatsfn(overridematchandpats)
398 oldmakefilematcher = logcmdutil._makenofollowfilematcher
398 oldmakefilematcher = logcmdutil._makenofollowfilematcher
399 setattr(logcmdutil, '_makenofollowfilematcher', overridemakefilematcher)
399 setattr(logcmdutil, '_makenofollowfilematcher', overridemakefilematcher)
400
400
401 try:
401 try:
402 return orig(ui, repo, *pats, **opts)
402 return orig(ui, repo, *pats, **opts)
403 finally:
403 finally:
404 restorematchandpatsfn()
404 restorematchandpatsfn()
405 setattr(logcmdutil, '_makenofollowfilematcher', oldmakefilematcher)
405 setattr(logcmdutil, '_makenofollowfilematcher', oldmakefilematcher)
406
406
407 def overrideverify(orig, ui, repo, *pats, **opts):
407 def overrideverify(orig, ui, repo, *pats, **opts):
408 large = opts.pop(r'large', False)
408 large = opts.pop(r'large', False)
409 all = opts.pop(r'lfa', False)
409 all = opts.pop(r'lfa', False)
410 contents = opts.pop(r'lfc', False)
410 contents = opts.pop(r'lfc', False)
411
411
412 result = orig(ui, repo, *pats, **opts)
412 result = orig(ui, repo, *pats, **opts)
413 if large or all or contents:
413 if large or all or contents:
414 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
414 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
415 return result
415 return result
416
416
417 def overridedebugstate(orig, ui, repo, *pats, **opts):
417 def overridedebugstate(orig, ui, repo, *pats, **opts):
418 large = opts.pop(r'large', False)
418 large = opts.pop(r'large', False)
419 if large:
419 if large:
420 class fakerepo(object):
420 class fakerepo(object):
421 dirstate = lfutil.openlfdirstate(ui, repo)
421 dirstate = lfutil.openlfdirstate(ui, repo)
422 orig(ui, fakerepo, *pats, **opts)
422 orig(ui, fakerepo, *pats, **opts)
423 else:
423 else:
424 orig(ui, repo, *pats, **opts)
424 orig(ui, repo, *pats, **opts)
425
425
426 # Before starting the manifest merge, merge.updates will call
426 # Before starting the manifest merge, merge.updates will call
427 # _checkunknownfile to check if there are any files in the merged-in
427 # _checkunknownfile to check if there are any files in the merged-in
428 # changeset that collide with unknown files in the working copy.
428 # changeset that collide with unknown files in the working copy.
429 #
429 #
430 # The largefiles are seen as unknown, so this prevents us from merging
430 # The largefiles are seen as unknown, so this prevents us from merging
431 # in a file 'foo' if we already have a largefile with the same name.
431 # in a file 'foo' if we already have a largefile with the same name.
432 #
432 #
433 # The overridden function filters the unknown files by removing any
433 # The overridden function filters the unknown files by removing any
434 # largefiles. This makes the merge proceed and we can then handle this
434 # largefiles. This makes the merge proceed and we can then handle this
435 # case further in the overridden calculateupdates function below.
435 # case further in the overridden calculateupdates function below.
436 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
436 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
437 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
437 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
438 return False
438 return False
439 return origfn(repo, wctx, mctx, f, f2)
439 return origfn(repo, wctx, mctx, f, f2)
440
440
441 # The manifest merge handles conflicts on the manifest level. We want
441 # The manifest merge handles conflicts on the manifest level. We want
442 # to handle changes in largefile-ness of files at this level too.
442 # to handle changes in largefile-ness of files at this level too.
443 #
443 #
444 # The strategy is to run the original calculateupdates and then process
444 # The strategy is to run the original calculateupdates and then process
445 # the action list it outputs. There are two cases we need to deal with:
445 # the action list it outputs. There are two cases we need to deal with:
446 #
446 #
447 # 1. Normal file in p1, largefile in p2. Here the largefile is
447 # 1. Normal file in p1, largefile in p2. Here the largefile is
448 # detected via its standin file, which will enter the working copy
448 # detected via its standin file, which will enter the working copy
449 # with a "get" action. It is not "merge" since the standin is all
449 # with a "get" action. It is not "merge" since the standin is all
450 # Mercurial is concerned with at this level -- the link to the
450 # Mercurial is concerned with at this level -- the link to the
451 # existing normal file is not relevant here.
451 # existing normal file is not relevant here.
452 #
452 #
453 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
453 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
454 # since the largefile will be present in the working copy and
454 # since the largefile will be present in the working copy and
455 # different from the normal file in p2. Mercurial therefore
455 # different from the normal file in p2. Mercurial therefore
456 # triggers a merge action.
456 # triggers a merge action.
457 #
457 #
458 # In both cases, we prompt the user and emit new actions to either
458 # In both cases, we prompt the user and emit new actions to either
459 # remove the standin (if the normal file was kept) or to remove the
459 # remove the standin (if the normal file was kept) or to remove the
460 # normal file and get the standin (if the largefile was kept). The
460 # normal file and get the standin (if the largefile was kept). The
461 # default prompt answer is to use the largefile version since it was
461 # default prompt answer is to use the largefile version since it was
462 # presumably changed on purpose.
462 # presumably changed on purpose.
463 #
463 #
464 # Finally, the merge.applyupdates function will then take care of
464 # Finally, the merge.applyupdates function will then take care of
465 # writing the files into the working copy and lfcommands.updatelfiles
465 # writing the files into the working copy and lfcommands.updatelfiles
466 # will update the largefiles.
466 # will update the largefiles.
467 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
467 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
468 acceptremote, *args, **kwargs):
468 acceptremote, *args, **kwargs):
469 overwrite = force and not branchmerge
469 overwrite = force and not branchmerge
470 actions, diverge, renamedelete = origfn(
470 actions, diverge, renamedelete = origfn(
471 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
471 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
472
472
473 if overwrite:
473 if overwrite:
474 return actions, diverge, renamedelete
474 return actions, diverge, renamedelete
475
475
476 # Convert to dictionary with filename as key and action as value.
476 # Convert to dictionary with filename as key and action as value.
477 lfiles = set()
477 lfiles = set()
478 for f in actions:
478 for f in actions:
479 splitstandin = lfutil.splitstandin(f)
479 splitstandin = lfutil.splitstandin(f)
480 if splitstandin in p1:
480 if splitstandin in p1:
481 lfiles.add(splitstandin)
481 lfiles.add(splitstandin)
482 elif lfutil.standin(f) in p1:
482 elif lfutil.standin(f) in p1:
483 lfiles.add(f)
483 lfiles.add(f)
484
484
485 for lfile in sorted(lfiles):
485 for lfile in sorted(lfiles):
486 standin = lfutil.standin(lfile)
486 standin = lfutil.standin(lfile)
487 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
487 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
488 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
488 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
489 if sm in ('g', 'dc') and lm != 'r':
489 if sm in ('g', 'dc') and lm != 'r':
490 if sm == 'dc':
490 if sm == 'dc':
491 f1, f2, fa, move, anc = sargs
491 f1, f2, fa, move, anc = sargs
492 sargs = (p2[f2].flags(), False)
492 sargs = (p2[f2].flags(), False)
493 # Case 1: normal file in the working copy, largefile in
493 # Case 1: normal file in the working copy, largefile in
494 # the second parent
494 # the second parent
495 usermsg = _('remote turned local normal file %s into a largefile\n'
495 usermsg = _('remote turned local normal file %s into a largefile\n'
496 'use (l)argefile or keep (n)ormal file?'
496 'use (l)argefile or keep (n)ormal file?'
497 '$$ &Largefile $$ &Normal file') % lfile
497 '$$ &Largefile $$ &Normal file') % lfile
498 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
498 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
499 actions[lfile] = ('r', None, 'replaced by standin')
499 actions[lfile] = ('r', None, 'replaced by standin')
500 actions[standin] = ('g', sargs, 'replaces standin')
500 actions[standin] = ('g', sargs, 'replaces standin')
501 else: # keep local normal file
501 else: # keep local normal file
502 actions[lfile] = ('k', None, 'replaces standin')
502 actions[lfile] = ('k', None, 'replaces standin')
503 if branchmerge:
503 if branchmerge:
504 actions[standin] = ('k', None, 'replaced by non-standin')
504 actions[standin] = ('k', None, 'replaced by non-standin')
505 else:
505 else:
506 actions[standin] = ('r', None, 'replaced by non-standin')
506 actions[standin] = ('r', None, 'replaced by non-standin')
507 elif lm in ('g', 'dc') and sm != 'r':
507 elif lm in ('g', 'dc') and sm != 'r':
508 if lm == 'dc':
508 if lm == 'dc':
509 f1, f2, fa, move, anc = largs
509 f1, f2, fa, move, anc = largs
510 largs = (p2[f2].flags(), False)
510 largs = (p2[f2].flags(), False)
511 # Case 2: largefile in the working copy, normal file in
511 # Case 2: largefile in the working copy, normal file in
512 # the second parent
512 # the second parent
513 usermsg = _('remote turned local largefile %s into a normal file\n'
513 usermsg = _('remote turned local largefile %s into a normal file\n'
514 'keep (l)argefile or use (n)ormal file?'
514 'keep (l)argefile or use (n)ormal file?'
515 '$$ &Largefile $$ &Normal file') % lfile
515 '$$ &Largefile $$ &Normal file') % lfile
516 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
516 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
517 if branchmerge:
517 if branchmerge:
518 # largefile can be restored from standin safely
518 # largefile can be restored from standin safely
519 actions[lfile] = ('k', None, 'replaced by standin')
519 actions[lfile] = ('k', None, 'replaced by standin')
520 actions[standin] = ('k', None, 'replaces standin')
520 actions[standin] = ('k', None, 'replaces standin')
521 else:
521 else:
522 # "lfile" should be marked as "removed" without
522 # "lfile" should be marked as "removed" without
523 # removal of itself
523 # removal of itself
524 actions[lfile] = ('lfmr', None,
524 actions[lfile] = ('lfmr', None,
525 'forget non-standin largefile')
525 'forget non-standin largefile')
526
526
527 # linear-merge should treat this largefile as 're-added'
527 # linear-merge should treat this largefile as 're-added'
528 actions[standin] = ('a', None, 'keep standin')
528 actions[standin] = ('a', None, 'keep standin')
529 else: # pick remote normal file
529 else: # pick remote normal file
530 actions[lfile] = ('g', largs, 'replaces standin')
530 actions[lfile] = ('g', largs, 'replaces standin')
531 actions[standin] = ('r', None, 'replaced by non-standin')
531 actions[standin] = ('r', None, 'replaced by non-standin')
532
532
533 return actions, diverge, renamedelete
533 return actions, diverge, renamedelete
534
534
535 def mergerecordupdates(orig, repo, actions, branchmerge):
535 def mergerecordupdates(orig, repo, actions, branchmerge):
536 if 'lfmr' in actions:
536 if 'lfmr' in actions:
537 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
537 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
538 for lfile, args, msg in actions['lfmr']:
538 for lfile, args, msg in actions['lfmr']:
539 # this should be executed before 'orig', to execute 'remove'
539 # this should be executed before 'orig', to execute 'remove'
540 # before all other actions
540 # before all other actions
541 repo.dirstate.remove(lfile)
541 repo.dirstate.remove(lfile)
542 # make sure lfile doesn't get synclfdirstate'd as normal
542 # make sure lfile doesn't get synclfdirstate'd as normal
543 lfdirstate.add(lfile)
543 lfdirstate.add(lfile)
544 lfdirstate.write()
544 lfdirstate.write()
545
545
546 return orig(repo, actions, branchmerge)
546 return orig(repo, actions, branchmerge)
547
547
548 # Override filemerge to prompt the user about how they wish to merge
548 # Override filemerge to prompt the user about how they wish to merge
549 # largefiles. This will handle identical edits without prompting the user.
549 # largefiles. This will handle identical edits without prompting the user.
550 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
550 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
551 labels=None):
551 labels=None):
552 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
552 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
553 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
553 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
554 labels=labels)
554 labels=labels)
555
555
556 ahash = lfutil.readasstandin(fca).lower()
556 ahash = lfutil.readasstandin(fca).lower()
557 dhash = lfutil.readasstandin(fcd).lower()
557 dhash = lfutil.readasstandin(fcd).lower()
558 ohash = lfutil.readasstandin(fco).lower()
558 ohash = lfutil.readasstandin(fco).lower()
559 if (ohash != ahash and
559 if (ohash != ahash and
560 ohash != dhash and
560 ohash != dhash and
561 (dhash == ahash or
561 (dhash == ahash or
562 repo.ui.promptchoice(
562 repo.ui.promptchoice(
563 _('largefile %s has a merge conflict\nancestor was %s\n'
563 _('largefile %s has a merge conflict\nancestor was %s\n'
564 'keep (l)ocal %s or\ntake (o)ther %s?'
564 'keep (l)ocal %s or\ntake (o)ther %s?'
565 '$$ &Local $$ &Other') %
565 '$$ &Local $$ &Other') %
566 (lfutil.splitstandin(orig), ahash, dhash, ohash),
566 (lfutil.splitstandin(orig), ahash, dhash, ohash),
567 0) == 1)):
567 0) == 1)):
568 repo.wwrite(fcd.path(), fco.data(), fco.flags())
568 repo.wwrite(fcd.path(), fco.data(), fco.flags())
569 return True, 0, False
569 return True, 0, False
570
570
571 def copiespathcopies(orig, ctx1, ctx2, match=None):
571 def copiespathcopies(orig, ctx1, ctx2, match=None):
572 copies = orig(ctx1, ctx2, match=match)
572 copies = orig(ctx1, ctx2, match=match)
573 updated = {}
573 updated = {}
574
574
575 for k, v in copies.iteritems():
575 for k, v in copies.iteritems():
576 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
576 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
577
577
578 return updated
578 return updated
579
579
580 # Copy first changes the matchers to match standins instead of
580 # Copy first changes the matchers to match standins instead of
581 # largefiles. Then it overrides util.copyfile in that function it
581 # largefiles. Then it overrides util.copyfile in that function it
582 # checks if the destination largefile already exists. It also keeps a
582 # checks if the destination largefile already exists. It also keeps a
583 # list of copied files so that the largefiles can be copied and the
583 # list of copied files so that the largefiles can be copied and the
584 # dirstate updated.
584 # dirstate updated.
585 def overridecopy(orig, ui, repo, pats, opts, rename=False):
585 def overridecopy(orig, ui, repo, pats, opts, rename=False):
586 # doesn't remove largefile on rename
586 # doesn't remove largefile on rename
587 if len(pats) < 2:
587 if len(pats) < 2:
588 # this isn't legal, let the original function deal with it
588 # this isn't legal, let the original function deal with it
589 return orig(ui, repo, pats, opts, rename)
589 return orig(ui, repo, pats, opts, rename)
590
590
591 # This could copy both lfiles and normal files in one command,
591 # This could copy both lfiles and normal files in one command,
592 # but we don't want to do that. First replace their matcher to
592 # but we don't want to do that. First replace their matcher to
593 # only match normal files and run it, then replace it to just
593 # only match normal files and run it, then replace it to just
594 # match largefiles and run it again.
594 # match largefiles and run it again.
595 nonormalfiles = False
595 nonormalfiles = False
596 nolfiles = False
596 nolfiles = False
597 installnormalfilesmatchfn(repo[None].manifest())
597 installnormalfilesmatchfn(repo[None].manifest())
598 try:
598 try:
599 result = orig(ui, repo, pats, opts, rename)
599 result = orig(ui, repo, pats, opts, rename)
600 except error.Abort as e:
600 except error.Abort as e:
601 if pycompat.bytestr(e) != _('no files to copy'):
601 if pycompat.bytestr(e) != _('no files to copy'):
602 raise e
602 raise e
603 else:
603 else:
604 nonormalfiles = True
604 nonormalfiles = True
605 result = 0
605 result = 0
606 finally:
606 finally:
607 restorematchfn()
607 restorematchfn()
608
608
609 # The first rename can cause our current working directory to be removed.
609 # The first rename can cause our current working directory to be removed.
610 # In that case there is nothing left to copy/rename so just quit.
610 # In that case there is nothing left to copy/rename so just quit.
611 try:
611 try:
612 repo.getcwd()
612 repo.getcwd()
613 except OSError:
613 except OSError:
614 return result
614 return result
615
615
616 def makestandin(relpath):
616 def makestandin(relpath):
617 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
617 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
618 return repo.wvfs.join(lfutil.standin(path))
618 return repo.wvfs.join(lfutil.standin(path))
619
619
620 fullpats = scmutil.expandpats(pats)
620 fullpats = scmutil.expandpats(pats)
621 dest = fullpats[-1]
621 dest = fullpats[-1]
622
622
623 if os.path.isdir(dest):
623 if os.path.isdir(dest):
624 if not os.path.isdir(makestandin(dest)):
624 if not os.path.isdir(makestandin(dest)):
625 os.makedirs(makestandin(dest))
625 os.makedirs(makestandin(dest))
626
626
627 try:
627 try:
628 # When we call orig below it creates the standins but we don't add
628 # When we call orig below it creates the standins but we don't add
629 # them to the dir state until later so lock during that time.
629 # them to the dir state until later so lock during that time.
630 wlock = repo.wlock()
630 wlock = repo.wlock()
631
631
632 manifest = repo[None].manifest()
632 manifest = repo[None].manifest()
633 def overridematch(ctx, pats=(), opts=None, globbed=False,
633 def overridematch(ctx, pats=(), opts=None, globbed=False,
634 default='relpath', badfn=None):
634 default='relpath', badfn=None):
635 if opts is None:
635 if opts is None:
636 opts = {}
636 opts = {}
637 newpats = []
637 newpats = []
638 # The patterns were previously mangled to add the standin
638 # The patterns were previously mangled to add the standin
639 # directory; we need to remove that now
639 # directory; we need to remove that now
640 for pat in pats:
640 for pat in pats:
641 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
641 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
642 newpats.append(pat.replace(lfutil.shortname, ''))
642 newpats.append(pat.replace(lfutil.shortname, ''))
643 else:
643 else:
644 newpats.append(pat)
644 newpats.append(pat)
645 match = oldmatch(ctx, newpats, opts, globbed, default, badfn=badfn)
645 match = oldmatch(ctx, newpats, opts, globbed, default, badfn=badfn)
646 m = copy.copy(match)
646 m = copy.copy(match)
647 lfile = lambda f: lfutil.standin(f) in manifest
647 lfile = lambda f: lfutil.standin(f) in manifest
648 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
648 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
649 m._fileset = set(m._files)
649 m._fileset = set(m._files)
650 origmatchfn = m.matchfn
650 origmatchfn = m.matchfn
651 def matchfn(f):
651 def matchfn(f):
652 lfile = lfutil.splitstandin(f)
652 lfile = lfutil.splitstandin(f)
653 return (lfile is not None and
653 return (lfile is not None and
654 (f in manifest) and
654 (f in manifest) and
655 origmatchfn(lfile) or
655 origmatchfn(lfile) or
656 None)
656 None)
657 m.matchfn = matchfn
657 m.matchfn = matchfn
658 return m
658 return m
659 oldmatch = installmatchfn(overridematch)
659 oldmatch = installmatchfn(overridematch)
660 listpats = []
660 listpats = []
661 for pat in pats:
661 for pat in pats:
662 if matchmod.patkind(pat) is not None:
662 if matchmod.patkind(pat) is not None:
663 listpats.append(pat)
663 listpats.append(pat)
664 else:
664 else:
665 listpats.append(makestandin(pat))
665 listpats.append(makestandin(pat))
666
666
667 try:
667 try:
668 origcopyfile = util.copyfile
668 origcopyfile = util.copyfile
669 copiedfiles = []
669 copiedfiles = []
670 def overridecopyfile(src, dest):
670 def overridecopyfile(src, dest, *args, **kwargs):
671 if (lfutil.shortname in src and
671 if (lfutil.shortname in src and
672 dest.startswith(repo.wjoin(lfutil.shortname))):
672 dest.startswith(repo.wjoin(lfutil.shortname))):
673 destlfile = dest.replace(lfutil.shortname, '')
673 destlfile = dest.replace(lfutil.shortname, '')
674 if not opts['force'] and os.path.exists(destlfile):
674 if not opts['force'] and os.path.exists(destlfile):
675 raise IOError('',
675 raise IOError('',
676 _('destination largefile already exists'))
676 _('destination largefile already exists'))
677 copiedfiles.append((src, dest))
677 copiedfiles.append((src, dest))
678 origcopyfile(src, dest)
678 origcopyfile(src, dest, *args, **kwargs)
679
679
680 util.copyfile = overridecopyfile
680 util.copyfile = overridecopyfile
681 result += orig(ui, repo, listpats, opts, rename)
681 result += orig(ui, repo, listpats, opts, rename)
682 finally:
682 finally:
683 util.copyfile = origcopyfile
683 util.copyfile = origcopyfile
684
684
685 lfdirstate = lfutil.openlfdirstate(ui, repo)
685 lfdirstate = lfutil.openlfdirstate(ui, repo)
686 for (src, dest) in copiedfiles:
686 for (src, dest) in copiedfiles:
687 if (lfutil.shortname in src and
687 if (lfutil.shortname in src and
688 dest.startswith(repo.wjoin(lfutil.shortname))):
688 dest.startswith(repo.wjoin(lfutil.shortname))):
689 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
689 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
690 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
690 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
691 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
691 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
692 if not os.path.isdir(destlfiledir):
692 if not os.path.isdir(destlfiledir):
693 os.makedirs(destlfiledir)
693 os.makedirs(destlfiledir)
694 if rename:
694 if rename:
695 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
695 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
696
696
697 # The file is gone, but this deletes any empty parent
697 # The file is gone, but this deletes any empty parent
698 # directories as a side-effect.
698 # directories as a side-effect.
699 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
699 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
700 lfdirstate.remove(srclfile)
700 lfdirstate.remove(srclfile)
701 else:
701 else:
702 util.copyfile(repo.wjoin(srclfile),
702 util.copyfile(repo.wjoin(srclfile),
703 repo.wjoin(destlfile))
703 repo.wjoin(destlfile))
704
704
705 lfdirstate.add(destlfile)
705 lfdirstate.add(destlfile)
706 lfdirstate.write()
706 lfdirstate.write()
707 except error.Abort as e:
707 except error.Abort as e:
708 if pycompat.bytestr(e) != _('no files to copy'):
708 if pycompat.bytestr(e) != _('no files to copy'):
709 raise e
709 raise e
710 else:
710 else:
711 nolfiles = True
711 nolfiles = True
712 finally:
712 finally:
713 restorematchfn()
713 restorematchfn()
714 wlock.release()
714 wlock.release()
715
715
716 if nolfiles and nonormalfiles:
716 if nolfiles and nonormalfiles:
717 raise error.Abort(_('no files to copy'))
717 raise error.Abort(_('no files to copy'))
718
718
719 return result
719 return result
720
720
721 # When the user calls revert, we have to be careful to not revert any
721 # When the user calls revert, we have to be careful to not revert any
722 # changes to other largefiles accidentally. This means we have to keep
722 # changes to other largefiles accidentally. This means we have to keep
723 # track of the largefiles that are being reverted so we only pull down
723 # track of the largefiles that are being reverted so we only pull down
724 # the necessary largefiles.
724 # the necessary largefiles.
725 #
725 #
726 # Standins are only updated (to match the hash of largefiles) before
726 # Standins are only updated (to match the hash of largefiles) before
727 # commits. Update the standins then run the original revert, changing
727 # commits. Update the standins then run the original revert, changing
728 # the matcher to hit standins instead of largefiles. Based on the
728 # the matcher to hit standins instead of largefiles. Based on the
729 # resulting standins update the largefiles.
729 # resulting standins update the largefiles.
730 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
730 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
731 # Because we put the standins in a bad state (by updating them)
731 # Because we put the standins in a bad state (by updating them)
732 # and then return them to a correct state we need to lock to
732 # and then return them to a correct state we need to lock to
733 # prevent others from changing them in their incorrect state.
733 # prevent others from changing them in their incorrect state.
734 with repo.wlock():
734 with repo.wlock():
735 lfdirstate = lfutil.openlfdirstate(ui, repo)
735 lfdirstate = lfutil.openlfdirstate(ui, repo)
736 s = lfutil.lfdirstatestatus(lfdirstate, repo)
736 s = lfutil.lfdirstatestatus(lfdirstate, repo)
737 lfdirstate.write()
737 lfdirstate.write()
738 for lfile in s.modified:
738 for lfile in s.modified:
739 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
739 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
740 for lfile in s.deleted:
740 for lfile in s.deleted:
741 fstandin = lfutil.standin(lfile)
741 fstandin = lfutil.standin(lfile)
742 if (repo.wvfs.exists(fstandin)):
742 if (repo.wvfs.exists(fstandin)):
743 repo.wvfs.unlink(fstandin)
743 repo.wvfs.unlink(fstandin)
744
744
745 oldstandins = lfutil.getstandinsstate(repo)
745 oldstandins = lfutil.getstandinsstate(repo)
746
746
747 def overridematch(mctx, pats=(), opts=None, globbed=False,
747 def overridematch(mctx, pats=(), opts=None, globbed=False,
748 default='relpath', badfn=None):
748 default='relpath', badfn=None):
749 if opts is None:
749 if opts is None:
750 opts = {}
750 opts = {}
751 match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn)
751 match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn)
752 m = copy.copy(match)
752 m = copy.copy(match)
753
753
754 # revert supports recursing into subrepos, and though largefiles
754 # revert supports recursing into subrepos, and though largefiles
755 # currently doesn't work correctly in that case, this match is
755 # currently doesn't work correctly in that case, this match is
756 # called, so the lfdirstate above may not be the correct one for
756 # called, so the lfdirstate above may not be the correct one for
757 # this invocation of match.
757 # this invocation of match.
758 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
758 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
759 False)
759 False)
760
760
761 wctx = repo[None]
761 wctx = repo[None]
762 matchfiles = []
762 matchfiles = []
763 for f in m._files:
763 for f in m._files:
764 standin = lfutil.standin(f)
764 standin = lfutil.standin(f)
765 if standin in ctx or standin in mctx:
765 if standin in ctx or standin in mctx:
766 matchfiles.append(standin)
766 matchfiles.append(standin)
767 elif standin in wctx or lfdirstate[f] == 'r':
767 elif standin in wctx or lfdirstate[f] == 'r':
768 continue
768 continue
769 else:
769 else:
770 matchfiles.append(f)
770 matchfiles.append(f)
771 m._files = matchfiles
771 m._files = matchfiles
772 m._fileset = set(m._files)
772 m._fileset = set(m._files)
773 origmatchfn = m.matchfn
773 origmatchfn = m.matchfn
774 def matchfn(f):
774 def matchfn(f):
775 lfile = lfutil.splitstandin(f)
775 lfile = lfutil.splitstandin(f)
776 if lfile is not None:
776 if lfile is not None:
777 return (origmatchfn(lfile) and
777 return (origmatchfn(lfile) and
778 (f in ctx or f in mctx))
778 (f in ctx or f in mctx))
779 return origmatchfn(f)
779 return origmatchfn(f)
780 m.matchfn = matchfn
780 m.matchfn = matchfn
781 return m
781 return m
782 oldmatch = installmatchfn(overridematch)
782 oldmatch = installmatchfn(overridematch)
783 try:
783 try:
784 orig(ui, repo, ctx, parents, *pats, **opts)
784 orig(ui, repo, ctx, parents, *pats, **opts)
785 finally:
785 finally:
786 restorematchfn()
786 restorematchfn()
787
787
788 newstandins = lfutil.getstandinsstate(repo)
788 newstandins = lfutil.getstandinsstate(repo)
789 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
789 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
790 # lfdirstate should be 'normallookup'-ed for updated files,
790 # lfdirstate should be 'normallookup'-ed for updated files,
791 # because reverting doesn't touch dirstate for 'normal' files
791 # because reverting doesn't touch dirstate for 'normal' files
792 # when target revision is explicitly specified: in such case,
792 # when target revision is explicitly specified: in such case,
793 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
793 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
794 # of target (standin) file.
794 # of target (standin) file.
795 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
795 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
796 normallookup=True)
796 normallookup=True)
797
797
798 # after pulling changesets, we need to take some extra care to get
798 # after pulling changesets, we need to take some extra care to get
799 # largefiles updated remotely
799 # largefiles updated remotely
800 def overridepull(orig, ui, repo, source=None, **opts):
800 def overridepull(orig, ui, repo, source=None, **opts):
801 revsprepull = len(repo)
801 revsprepull = len(repo)
802 if not source:
802 if not source:
803 source = 'default'
803 source = 'default'
804 repo.lfpullsource = source
804 repo.lfpullsource = source
805 result = orig(ui, repo, source, **opts)
805 result = orig(ui, repo, source, **opts)
806 revspostpull = len(repo)
806 revspostpull = len(repo)
807 lfrevs = opts.get(r'lfrev', [])
807 lfrevs = opts.get(r'lfrev', [])
808 if opts.get(r'all_largefiles'):
808 if opts.get(r'all_largefiles'):
809 lfrevs.append('pulled()')
809 lfrevs.append('pulled()')
810 if lfrevs and revspostpull > revsprepull:
810 if lfrevs and revspostpull > revsprepull:
811 numcached = 0
811 numcached = 0
812 repo.firstpulled = revsprepull # for pulled() revset expression
812 repo.firstpulled = revsprepull # for pulled() revset expression
813 try:
813 try:
814 for rev in scmutil.revrange(repo, lfrevs):
814 for rev in scmutil.revrange(repo, lfrevs):
815 ui.note(_('pulling largefiles for revision %d\n') % rev)
815 ui.note(_('pulling largefiles for revision %d\n') % rev)
816 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
816 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
817 numcached += len(cached)
817 numcached += len(cached)
818 finally:
818 finally:
819 del repo.firstpulled
819 del repo.firstpulled
820 ui.status(_("%d largefiles cached\n") % numcached)
820 ui.status(_("%d largefiles cached\n") % numcached)
821 return result
821 return result
822
822
823 def overridepush(orig, ui, repo, *args, **kwargs):
823 def overridepush(orig, ui, repo, *args, **kwargs):
824 """Override push command and store --lfrev parameters in opargs"""
824 """Override push command and store --lfrev parameters in opargs"""
825 lfrevs = kwargs.pop(r'lfrev', None)
825 lfrevs = kwargs.pop(r'lfrev', None)
826 if lfrevs:
826 if lfrevs:
827 opargs = kwargs.setdefault(r'opargs', {})
827 opargs = kwargs.setdefault(r'opargs', {})
828 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
828 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
829 return orig(ui, repo, *args, **kwargs)
829 return orig(ui, repo, *args, **kwargs)
830
830
831 def exchangepushoperation(orig, *args, **kwargs):
831 def exchangepushoperation(orig, *args, **kwargs):
832 """Override pushoperation constructor and store lfrevs parameter"""
832 """Override pushoperation constructor and store lfrevs parameter"""
833 lfrevs = kwargs.pop(r'lfrevs', None)
833 lfrevs = kwargs.pop(r'lfrevs', None)
834 pushop = orig(*args, **kwargs)
834 pushop = orig(*args, **kwargs)
835 pushop.lfrevs = lfrevs
835 pushop.lfrevs = lfrevs
836 return pushop
836 return pushop
837
837
838 revsetpredicate = registrar.revsetpredicate()
838 revsetpredicate = registrar.revsetpredicate()
839
839
840 @revsetpredicate('pulled()')
840 @revsetpredicate('pulled()')
841 def pulledrevsetsymbol(repo, subset, x):
841 def pulledrevsetsymbol(repo, subset, x):
842 """Changesets that just has been pulled.
842 """Changesets that just has been pulled.
843
843
844 Only available with largefiles from pull --lfrev expressions.
844 Only available with largefiles from pull --lfrev expressions.
845
845
846 .. container:: verbose
846 .. container:: verbose
847
847
848 Some examples:
848 Some examples:
849
849
850 - pull largefiles for all new changesets::
850 - pull largefiles for all new changesets::
851
851
852 hg pull -lfrev "pulled()"
852 hg pull -lfrev "pulled()"
853
853
854 - pull largefiles for all new branch heads::
854 - pull largefiles for all new branch heads::
855
855
856 hg pull -lfrev "head(pulled()) and not closed()"
856 hg pull -lfrev "head(pulled()) and not closed()"
857
857
858 """
858 """
859
859
860 try:
860 try:
861 firstpulled = repo.firstpulled
861 firstpulled = repo.firstpulled
862 except AttributeError:
862 except AttributeError:
863 raise error.Abort(_("pulled() only available in --lfrev"))
863 raise error.Abort(_("pulled() only available in --lfrev"))
864 return smartset.baseset([r for r in subset if r >= firstpulled])
864 return smartset.baseset([r for r in subset if r >= firstpulled])
865
865
866 def overrideclone(orig, ui, source, dest=None, **opts):
866 def overrideclone(orig, ui, source, dest=None, **opts):
867 d = dest
867 d = dest
868 if d is None:
868 if d is None:
869 d = hg.defaultdest(source)
869 d = hg.defaultdest(source)
870 if opts.get(r'all_largefiles') and not hg.islocal(d):
870 if opts.get(r'all_largefiles') and not hg.islocal(d):
871 raise error.Abort(_(
871 raise error.Abort(_(
872 '--all-largefiles is incompatible with non-local destination %s') %
872 '--all-largefiles is incompatible with non-local destination %s') %
873 d)
873 d)
874
874
875 return orig(ui, source, dest, **opts)
875 return orig(ui, source, dest, **opts)
876
876
877 def hgclone(orig, ui, opts, *args, **kwargs):
877 def hgclone(orig, ui, opts, *args, **kwargs):
878 result = orig(ui, opts, *args, **kwargs)
878 result = orig(ui, opts, *args, **kwargs)
879
879
880 if result is not None:
880 if result is not None:
881 sourcerepo, destrepo = result
881 sourcerepo, destrepo = result
882 repo = destrepo.local()
882 repo = destrepo.local()
883
883
884 # When cloning to a remote repo (like through SSH), no repo is available
884 # When cloning to a remote repo (like through SSH), no repo is available
885 # from the peer. Therefore the largefiles can't be downloaded and the
885 # from the peer. Therefore the largefiles can't be downloaded and the
886 # hgrc can't be updated.
886 # hgrc can't be updated.
887 if not repo:
887 if not repo:
888 return result
888 return result
889
889
890 # If largefiles is required for this repo, permanently enable it locally
890 # If largefiles is required for this repo, permanently enable it locally
891 if 'largefiles' in repo.requirements:
891 if 'largefiles' in repo.requirements:
892 repo.vfs.append('hgrc',
892 repo.vfs.append('hgrc',
893 util.tonativeeol('\n[extensions]\nlargefiles=\n'))
893 util.tonativeeol('\n[extensions]\nlargefiles=\n'))
894
894
895 # Caching is implicitly limited to 'rev' option, since the dest repo was
895 # Caching is implicitly limited to 'rev' option, since the dest repo was
896 # truncated at that point. The user may expect a download count with
896 # truncated at that point. The user may expect a download count with
897 # this option, so attempt whether or not this is a largefile repo.
897 # this option, so attempt whether or not this is a largefile repo.
898 if opts.get(r'all_largefiles'):
898 if opts.get(r'all_largefiles'):
899 success, missing = lfcommands.downloadlfiles(ui, repo, None)
899 success, missing = lfcommands.downloadlfiles(ui, repo, None)
900
900
901 if missing != 0:
901 if missing != 0:
902 return None
902 return None
903
903
904 return result
904 return result
905
905
906 def hgpostshare(orig, sourcerepo, destrepo, bookmarks=True, defaultpath=None):
906 def hgpostshare(orig, sourcerepo, destrepo, bookmarks=True, defaultpath=None):
907 orig(sourcerepo, destrepo, bookmarks, defaultpath)
907 orig(sourcerepo, destrepo, bookmarks, defaultpath)
908
908
909 # If largefiles is required for this repo, permanently enable it locally
909 # If largefiles is required for this repo, permanently enable it locally
910 if 'largefiles' in destrepo.requirements:
910 if 'largefiles' in destrepo.requirements:
911 destrepo.vfs.append('hgrc',
911 destrepo.vfs.append('hgrc',
912 util.tonativeeol('\n[extensions]\nlargefiles=\n'))
912 util.tonativeeol('\n[extensions]\nlargefiles=\n'))
913
913
914 def overriderebase(orig, ui, repo, **opts):
914 def overriderebase(orig, ui, repo, **opts):
915 if not util.safehasattr(repo, '_largefilesenabled'):
915 if not util.safehasattr(repo, '_largefilesenabled'):
916 return orig(ui, repo, **opts)
916 return orig(ui, repo, **opts)
917
917
918 resuming = opts.get(r'continue')
918 resuming = opts.get(r'continue')
919 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
919 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
920 repo._lfstatuswriters.append(lambda *msg, **opts: None)
920 repo._lfstatuswriters.append(lambda *msg, **opts: None)
921 try:
921 try:
922 return orig(ui, repo, **opts)
922 return orig(ui, repo, **opts)
923 finally:
923 finally:
924 repo._lfstatuswriters.pop()
924 repo._lfstatuswriters.pop()
925 repo._lfcommithooks.pop()
925 repo._lfcommithooks.pop()
926
926
927 def overridearchivecmd(orig, ui, repo, dest, **opts):
927 def overridearchivecmd(orig, ui, repo, dest, **opts):
928 repo.unfiltered().lfstatus = True
928 repo.unfiltered().lfstatus = True
929
929
930 try:
930 try:
931 return orig(ui, repo.unfiltered(), dest, **opts)
931 return orig(ui, repo.unfiltered(), dest, **opts)
932 finally:
932 finally:
933 repo.unfiltered().lfstatus = False
933 repo.unfiltered().lfstatus = False
934
934
935 def hgwebarchive(orig, web):
935 def hgwebarchive(orig, web):
936 web.repo.lfstatus = True
936 web.repo.lfstatus = True
937
937
938 try:
938 try:
939 return orig(web)
939 return orig(web)
940 finally:
940 finally:
941 web.repo.lfstatus = False
941 web.repo.lfstatus = False
942
942
943 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
943 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
944 prefix='', mtime=None, subrepos=None):
944 prefix='', mtime=None, subrepos=None):
945 # For some reason setting repo.lfstatus in hgwebarchive only changes the
945 # For some reason setting repo.lfstatus in hgwebarchive only changes the
946 # unfiltered repo's attr, so check that as well.
946 # unfiltered repo's attr, so check that as well.
947 if not repo.lfstatus and not repo.unfiltered().lfstatus:
947 if not repo.lfstatus and not repo.unfiltered().lfstatus:
948 return orig(repo, dest, node, kind, decode, matchfn, prefix, mtime,
948 return orig(repo, dest, node, kind, decode, matchfn, prefix, mtime,
949 subrepos)
949 subrepos)
950
950
951 # No need to lock because we are only reading history and
951 # No need to lock because we are only reading history and
952 # largefile caches, neither of which are modified.
952 # largefile caches, neither of which are modified.
953 if node is not None:
953 if node is not None:
954 lfcommands.cachelfiles(repo.ui, repo, node)
954 lfcommands.cachelfiles(repo.ui, repo, node)
955
955
956 if kind not in archival.archivers:
956 if kind not in archival.archivers:
957 raise error.Abort(_("unknown archive type '%s'") % kind)
957 raise error.Abort(_("unknown archive type '%s'") % kind)
958
958
959 ctx = repo[node]
959 ctx = repo[node]
960
960
961 if kind == 'files':
961 if kind == 'files':
962 if prefix:
962 if prefix:
963 raise error.Abort(
963 raise error.Abort(
964 _('cannot give prefix when archiving to files'))
964 _('cannot give prefix when archiving to files'))
965 else:
965 else:
966 prefix = archival.tidyprefix(dest, kind, prefix)
966 prefix = archival.tidyprefix(dest, kind, prefix)
967
967
968 def write(name, mode, islink, getdata):
968 def write(name, mode, islink, getdata):
969 if matchfn and not matchfn(name):
969 if matchfn and not matchfn(name):
970 return
970 return
971 data = getdata()
971 data = getdata()
972 if decode:
972 if decode:
973 data = repo.wwritedata(name, data)
973 data = repo.wwritedata(name, data)
974 archiver.addfile(prefix + name, mode, islink, data)
974 archiver.addfile(prefix + name, mode, islink, data)
975
975
976 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
976 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
977
977
978 if repo.ui.configbool("ui", "archivemeta"):
978 if repo.ui.configbool("ui", "archivemeta"):
979 write('.hg_archival.txt', 0o644, False,
979 write('.hg_archival.txt', 0o644, False,
980 lambda: archival.buildmetadata(ctx))
980 lambda: archival.buildmetadata(ctx))
981
981
982 for f in ctx:
982 for f in ctx:
983 ff = ctx.flags(f)
983 ff = ctx.flags(f)
984 getdata = ctx[f].data
984 getdata = ctx[f].data
985 lfile = lfutil.splitstandin(f)
985 lfile = lfutil.splitstandin(f)
986 if lfile is not None:
986 if lfile is not None:
987 if node is not None:
987 if node is not None:
988 path = lfutil.findfile(repo, getdata().strip())
988 path = lfutil.findfile(repo, getdata().strip())
989
989
990 if path is None:
990 if path is None:
991 raise error.Abort(
991 raise error.Abort(
992 _('largefile %s not found in repo store or system cache')
992 _('largefile %s not found in repo store or system cache')
993 % lfile)
993 % lfile)
994 else:
994 else:
995 path = lfile
995 path = lfile
996
996
997 f = lfile
997 f = lfile
998
998
999 getdata = lambda: util.readfile(path)
999 getdata = lambda: util.readfile(path)
1000 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1000 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1001
1001
1002 if subrepos:
1002 if subrepos:
1003 for subpath in sorted(ctx.substate):
1003 for subpath in sorted(ctx.substate):
1004 sub = ctx.workingsub(subpath)
1004 sub = ctx.workingsub(subpath)
1005 submatch = matchmod.subdirmatcher(subpath, matchfn)
1005 submatch = matchmod.subdirmatcher(subpath, matchfn)
1006 sub._repo.lfstatus = True
1006 sub._repo.lfstatus = True
1007 sub.archive(archiver, prefix, submatch)
1007 sub.archive(archiver, prefix, submatch)
1008
1008
1009 archiver.done()
1009 archiver.done()
1010
1010
1011 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1011 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1012 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1012 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1013 if not lfenabled or not repo._repo.lfstatus:
1013 if not lfenabled or not repo._repo.lfstatus:
1014 return orig(repo, archiver, prefix, match, decode)
1014 return orig(repo, archiver, prefix, match, decode)
1015
1015
1016 repo._get(repo._state + ('hg',))
1016 repo._get(repo._state + ('hg',))
1017 rev = repo._state[1]
1017 rev = repo._state[1]
1018 ctx = repo._repo[rev]
1018 ctx = repo._repo[rev]
1019
1019
1020 if ctx.node() is not None:
1020 if ctx.node() is not None:
1021 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1021 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1022
1022
1023 def write(name, mode, islink, getdata):
1023 def write(name, mode, islink, getdata):
1024 # At this point, the standin has been replaced with the largefile name,
1024 # At this point, the standin has been replaced with the largefile name,
1025 # so the normal matcher works here without the lfutil variants.
1025 # so the normal matcher works here without the lfutil variants.
1026 if match and not match(f):
1026 if match and not match(f):
1027 return
1027 return
1028 data = getdata()
1028 data = getdata()
1029 if decode:
1029 if decode:
1030 data = repo._repo.wwritedata(name, data)
1030 data = repo._repo.wwritedata(name, data)
1031
1031
1032 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
1032 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
1033
1033
1034 for f in ctx:
1034 for f in ctx:
1035 ff = ctx.flags(f)
1035 ff = ctx.flags(f)
1036 getdata = ctx[f].data
1036 getdata = ctx[f].data
1037 lfile = lfutil.splitstandin(f)
1037 lfile = lfutil.splitstandin(f)
1038 if lfile is not None:
1038 if lfile is not None:
1039 if ctx.node() is not None:
1039 if ctx.node() is not None:
1040 path = lfutil.findfile(repo._repo, getdata().strip())
1040 path = lfutil.findfile(repo._repo, getdata().strip())
1041
1041
1042 if path is None:
1042 if path is None:
1043 raise error.Abort(
1043 raise error.Abort(
1044 _('largefile %s not found in repo store or system cache')
1044 _('largefile %s not found in repo store or system cache')
1045 % lfile)
1045 % lfile)
1046 else:
1046 else:
1047 path = lfile
1047 path = lfile
1048
1048
1049 f = lfile
1049 f = lfile
1050
1050
1051 getdata = lambda: util.readfile(os.path.join(prefix, path))
1051 getdata = lambda: util.readfile(os.path.join(prefix, path))
1052
1052
1053 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1053 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1054
1054
1055 for subpath in sorted(ctx.substate):
1055 for subpath in sorted(ctx.substate):
1056 sub = ctx.workingsub(subpath)
1056 sub = ctx.workingsub(subpath)
1057 submatch = matchmod.subdirmatcher(subpath, match)
1057 submatch = matchmod.subdirmatcher(subpath, match)
1058 sub._repo.lfstatus = True
1058 sub._repo.lfstatus = True
1059 sub.archive(archiver, prefix + repo._path + '/', submatch, decode)
1059 sub.archive(archiver, prefix + repo._path + '/', submatch, decode)
1060
1060
1061 # If a largefile is modified, the change is not reflected in its
1061 # If a largefile is modified, the change is not reflected in its
1062 # standin until a commit. cmdutil.bailifchanged() raises an exception
1062 # standin until a commit. cmdutil.bailifchanged() raises an exception
1063 # if the repo has uncommitted changes. Wrap it to also check if
1063 # if the repo has uncommitted changes. Wrap it to also check if
1064 # largefiles were changed. This is used by bisect, backout and fetch.
1064 # largefiles were changed. This is used by bisect, backout and fetch.
1065 def overridebailifchanged(orig, repo, *args, **kwargs):
1065 def overridebailifchanged(orig, repo, *args, **kwargs):
1066 orig(repo, *args, **kwargs)
1066 orig(repo, *args, **kwargs)
1067 repo.lfstatus = True
1067 repo.lfstatus = True
1068 s = repo.status()
1068 s = repo.status()
1069 repo.lfstatus = False
1069 repo.lfstatus = False
1070 if s.modified or s.added or s.removed or s.deleted:
1070 if s.modified or s.added or s.removed or s.deleted:
1071 raise error.Abort(_('uncommitted changes'))
1071 raise error.Abort(_('uncommitted changes'))
1072
1072
1073 def postcommitstatus(orig, repo, *args, **kwargs):
1073 def postcommitstatus(orig, repo, *args, **kwargs):
1074 repo.lfstatus = True
1074 repo.lfstatus = True
1075 try:
1075 try:
1076 return orig(repo, *args, **kwargs)
1076 return orig(repo, *args, **kwargs)
1077 finally:
1077 finally:
1078 repo.lfstatus = False
1078 repo.lfstatus = False
1079
1079
1080 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun):
1080 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun):
1081 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1081 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1082 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun)
1082 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun)
1083 m = composelargefilematcher(match, repo[None].manifest())
1083 m = composelargefilematcher(match, repo[None].manifest())
1084
1084
1085 try:
1085 try:
1086 repo.lfstatus = True
1086 repo.lfstatus = True
1087 s = repo.status(match=m, clean=True)
1087 s = repo.status(match=m, clean=True)
1088 finally:
1088 finally:
1089 repo.lfstatus = False
1089 repo.lfstatus = False
1090 manifest = repo[None].manifest()
1090 manifest = repo[None].manifest()
1091 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1091 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1092 forget = [f for f in forget if lfutil.standin(f) in manifest]
1092 forget = [f for f in forget if lfutil.standin(f) in manifest]
1093
1093
1094 for f in forget:
1094 for f in forget:
1095 fstandin = lfutil.standin(f)
1095 fstandin = lfutil.standin(f)
1096 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1096 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1097 ui.warn(_('not removing %s: file is already untracked\n')
1097 ui.warn(_('not removing %s: file is already untracked\n')
1098 % m.rel(f))
1098 % m.rel(f))
1099 bad.append(f)
1099 bad.append(f)
1100
1100
1101 for f in forget:
1101 for f in forget:
1102 if ui.verbose or not m.exact(f):
1102 if ui.verbose or not m.exact(f):
1103 ui.status(_('removing %s\n') % m.rel(f))
1103 ui.status(_('removing %s\n') % m.rel(f))
1104
1104
1105 # Need to lock because standin files are deleted then removed from the
1105 # Need to lock because standin files are deleted then removed from the
1106 # repository and we could race in-between.
1106 # repository and we could race in-between.
1107 with repo.wlock():
1107 with repo.wlock():
1108 lfdirstate = lfutil.openlfdirstate(ui, repo)
1108 lfdirstate = lfutil.openlfdirstate(ui, repo)
1109 for f in forget:
1109 for f in forget:
1110 if lfdirstate[f] == 'a':
1110 if lfdirstate[f] == 'a':
1111 lfdirstate.drop(f)
1111 lfdirstate.drop(f)
1112 else:
1112 else:
1113 lfdirstate.remove(f)
1113 lfdirstate.remove(f)
1114 lfdirstate.write()
1114 lfdirstate.write()
1115 standins = [lfutil.standin(f) for f in forget]
1115 standins = [lfutil.standin(f) for f in forget]
1116 for f in standins:
1116 for f in standins:
1117 repo.wvfs.unlinkpath(f, ignoremissing=True)
1117 repo.wvfs.unlinkpath(f, ignoremissing=True)
1118 rejected = repo[None].forget(standins)
1118 rejected = repo[None].forget(standins)
1119
1119
1120 bad.extend(f for f in rejected if f in m.files())
1120 bad.extend(f for f in rejected if f in m.files())
1121 forgot.extend(f for f in forget if f not in rejected)
1121 forgot.extend(f for f in forget if f not in rejected)
1122 return bad, forgot
1122 return bad, forgot
1123
1123
1124 def _getoutgoings(repo, other, missing, addfunc):
1124 def _getoutgoings(repo, other, missing, addfunc):
1125 """get pairs of filename and largefile hash in outgoing revisions
1125 """get pairs of filename and largefile hash in outgoing revisions
1126 in 'missing'.
1126 in 'missing'.
1127
1127
1128 largefiles already existing on 'other' repository are ignored.
1128 largefiles already existing on 'other' repository are ignored.
1129
1129
1130 'addfunc' is invoked with each unique pairs of filename and
1130 'addfunc' is invoked with each unique pairs of filename and
1131 largefile hash value.
1131 largefile hash value.
1132 """
1132 """
1133 knowns = set()
1133 knowns = set()
1134 lfhashes = set()
1134 lfhashes = set()
1135 def dedup(fn, lfhash):
1135 def dedup(fn, lfhash):
1136 k = (fn, lfhash)
1136 k = (fn, lfhash)
1137 if k not in knowns:
1137 if k not in knowns:
1138 knowns.add(k)
1138 knowns.add(k)
1139 lfhashes.add(lfhash)
1139 lfhashes.add(lfhash)
1140 lfutil.getlfilestoupload(repo, missing, dedup)
1140 lfutil.getlfilestoupload(repo, missing, dedup)
1141 if lfhashes:
1141 if lfhashes:
1142 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1142 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1143 for fn, lfhash in knowns:
1143 for fn, lfhash in knowns:
1144 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1144 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1145 addfunc(fn, lfhash)
1145 addfunc(fn, lfhash)
1146
1146
1147 def outgoinghook(ui, repo, other, opts, missing):
1147 def outgoinghook(ui, repo, other, opts, missing):
1148 if opts.pop('large', None):
1148 if opts.pop('large', None):
1149 lfhashes = set()
1149 lfhashes = set()
1150 if ui.debugflag:
1150 if ui.debugflag:
1151 toupload = {}
1151 toupload = {}
1152 def addfunc(fn, lfhash):
1152 def addfunc(fn, lfhash):
1153 if fn not in toupload:
1153 if fn not in toupload:
1154 toupload[fn] = []
1154 toupload[fn] = []
1155 toupload[fn].append(lfhash)
1155 toupload[fn].append(lfhash)
1156 lfhashes.add(lfhash)
1156 lfhashes.add(lfhash)
1157 def showhashes(fn):
1157 def showhashes(fn):
1158 for lfhash in sorted(toupload[fn]):
1158 for lfhash in sorted(toupload[fn]):
1159 ui.debug(' %s\n' % (lfhash))
1159 ui.debug(' %s\n' % (lfhash))
1160 else:
1160 else:
1161 toupload = set()
1161 toupload = set()
1162 def addfunc(fn, lfhash):
1162 def addfunc(fn, lfhash):
1163 toupload.add(fn)
1163 toupload.add(fn)
1164 lfhashes.add(lfhash)
1164 lfhashes.add(lfhash)
1165 def showhashes(fn):
1165 def showhashes(fn):
1166 pass
1166 pass
1167 _getoutgoings(repo, other, missing, addfunc)
1167 _getoutgoings(repo, other, missing, addfunc)
1168
1168
1169 if not toupload:
1169 if not toupload:
1170 ui.status(_('largefiles: no files to upload\n'))
1170 ui.status(_('largefiles: no files to upload\n'))
1171 else:
1171 else:
1172 ui.status(_('largefiles to upload (%d entities):\n')
1172 ui.status(_('largefiles to upload (%d entities):\n')
1173 % (len(lfhashes)))
1173 % (len(lfhashes)))
1174 for file in sorted(toupload):
1174 for file in sorted(toupload):
1175 ui.status(lfutil.splitstandin(file) + '\n')
1175 ui.status(lfutil.splitstandin(file) + '\n')
1176 showhashes(file)
1176 showhashes(file)
1177 ui.status('\n')
1177 ui.status('\n')
1178
1178
1179 def summaryremotehook(ui, repo, opts, changes):
1179 def summaryremotehook(ui, repo, opts, changes):
1180 largeopt = opts.get('large', False)
1180 largeopt = opts.get('large', False)
1181 if changes is None:
1181 if changes is None:
1182 if largeopt:
1182 if largeopt:
1183 return (False, True) # only outgoing check is needed
1183 return (False, True) # only outgoing check is needed
1184 else:
1184 else:
1185 return (False, False)
1185 return (False, False)
1186 elif largeopt:
1186 elif largeopt:
1187 url, branch, peer, outgoing = changes[1]
1187 url, branch, peer, outgoing = changes[1]
1188 if peer is None:
1188 if peer is None:
1189 # i18n: column positioning for "hg summary"
1189 # i18n: column positioning for "hg summary"
1190 ui.status(_('largefiles: (no remote repo)\n'))
1190 ui.status(_('largefiles: (no remote repo)\n'))
1191 return
1191 return
1192
1192
1193 toupload = set()
1193 toupload = set()
1194 lfhashes = set()
1194 lfhashes = set()
1195 def addfunc(fn, lfhash):
1195 def addfunc(fn, lfhash):
1196 toupload.add(fn)
1196 toupload.add(fn)
1197 lfhashes.add(lfhash)
1197 lfhashes.add(lfhash)
1198 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1198 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1199
1199
1200 if not toupload:
1200 if not toupload:
1201 # i18n: column positioning for "hg summary"
1201 # i18n: column positioning for "hg summary"
1202 ui.status(_('largefiles: (no files to upload)\n'))
1202 ui.status(_('largefiles: (no files to upload)\n'))
1203 else:
1203 else:
1204 # i18n: column positioning for "hg summary"
1204 # i18n: column positioning for "hg summary"
1205 ui.status(_('largefiles: %d entities for %d files to upload\n')
1205 ui.status(_('largefiles: %d entities for %d files to upload\n')
1206 % (len(lfhashes), len(toupload)))
1206 % (len(lfhashes), len(toupload)))
1207
1207
1208 def overridesummary(orig, ui, repo, *pats, **opts):
1208 def overridesummary(orig, ui, repo, *pats, **opts):
1209 try:
1209 try:
1210 repo.lfstatus = True
1210 repo.lfstatus = True
1211 orig(ui, repo, *pats, **opts)
1211 orig(ui, repo, *pats, **opts)
1212 finally:
1212 finally:
1213 repo.lfstatus = False
1213 repo.lfstatus = False
1214
1214
1215 def scmutiladdremove(orig, repo, matcher, prefix, opts=None, dry_run=None,
1215 def scmutiladdremove(orig, repo, matcher, prefix, opts=None, dry_run=None,
1216 similarity=None):
1216 similarity=None):
1217 if opts is None:
1217 if opts is None:
1218 opts = {}
1218 opts = {}
1219 if not lfutil.islfilesrepo(repo):
1219 if not lfutil.islfilesrepo(repo):
1220 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1220 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1221 # Get the list of missing largefiles so we can remove them
1221 # Get the list of missing largefiles so we can remove them
1222 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1222 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1223 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
1223 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
1224 subrepos=[], ignored=False, clean=False,
1224 subrepos=[], ignored=False, clean=False,
1225 unknown=False)
1225 unknown=False)
1226
1226
1227 # Call into the normal remove code, but the removing of the standin, we want
1227 # Call into the normal remove code, but the removing of the standin, we want
1228 # to have handled by original addremove. Monkey patching here makes sure
1228 # to have handled by original addremove. Monkey patching here makes sure
1229 # we don't remove the standin in the largefiles code, preventing a very
1229 # we don't remove the standin in the largefiles code, preventing a very
1230 # confused state later.
1230 # confused state later.
1231 if s.deleted:
1231 if s.deleted:
1232 m = copy.copy(matcher)
1232 m = copy.copy(matcher)
1233
1233
1234 # The m._files and m._map attributes are not changed to the deleted list
1234 # The m._files and m._map attributes are not changed to the deleted list
1235 # because that affects the m.exact() test, which in turn governs whether
1235 # because that affects the m.exact() test, which in turn governs whether
1236 # or not the file name is printed, and how. Simply limit the original
1236 # or not the file name is printed, and how. Simply limit the original
1237 # matches to those in the deleted status list.
1237 # matches to those in the deleted status list.
1238 matchfn = m.matchfn
1238 matchfn = m.matchfn
1239 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1239 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1240
1240
1241 removelargefiles(repo.ui, repo, True, m, **pycompat.strkwargs(opts))
1241 removelargefiles(repo.ui, repo, True, m, **pycompat.strkwargs(opts))
1242 # Call into the normal add code, and any files that *should* be added as
1242 # Call into the normal add code, and any files that *should* be added as
1243 # largefiles will be
1243 # largefiles will be
1244 added, bad = addlargefiles(repo.ui, repo, True, matcher,
1244 added, bad = addlargefiles(repo.ui, repo, True, matcher,
1245 **pycompat.strkwargs(opts))
1245 **pycompat.strkwargs(opts))
1246 # Now that we've handled largefiles, hand off to the original addremove
1246 # Now that we've handled largefiles, hand off to the original addremove
1247 # function to take care of the rest. Make sure it doesn't do anything with
1247 # function to take care of the rest. Make sure it doesn't do anything with
1248 # largefiles by passing a matcher that will ignore them.
1248 # largefiles by passing a matcher that will ignore them.
1249 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1249 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1250 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1250 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1251
1251
1252 # Calling purge with --all will cause the largefiles to be deleted.
1252 # Calling purge with --all will cause the largefiles to be deleted.
1253 # Override repo.status to prevent this from happening.
1253 # Override repo.status to prevent this from happening.
1254 def overridepurge(orig, ui, repo, *dirs, **opts):
1254 def overridepurge(orig, ui, repo, *dirs, **opts):
1255 # XXX Monkey patching a repoview will not work. The assigned attribute will
1255 # XXX Monkey patching a repoview will not work. The assigned attribute will
1256 # be set on the unfiltered repo, but we will only lookup attributes in the
1256 # be set on the unfiltered repo, but we will only lookup attributes in the
1257 # unfiltered repo if the lookup in the repoview object itself fails. As the
1257 # unfiltered repo if the lookup in the repoview object itself fails. As the
1258 # monkey patched method exists on the repoview class the lookup will not
1258 # monkey patched method exists on the repoview class the lookup will not
1259 # fail. As a result, the original version will shadow the monkey patched
1259 # fail. As a result, the original version will shadow the monkey patched
1260 # one, defeating the monkey patch.
1260 # one, defeating the monkey patch.
1261 #
1261 #
1262 # As a work around we use an unfiltered repo here. We should do something
1262 # As a work around we use an unfiltered repo here. We should do something
1263 # cleaner instead.
1263 # cleaner instead.
1264 repo = repo.unfiltered()
1264 repo = repo.unfiltered()
1265 oldstatus = repo.status
1265 oldstatus = repo.status
1266 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1266 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1267 clean=False, unknown=False, listsubrepos=False):
1267 clean=False, unknown=False, listsubrepos=False):
1268 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1268 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1269 listsubrepos)
1269 listsubrepos)
1270 lfdirstate = lfutil.openlfdirstate(ui, repo)
1270 lfdirstate = lfutil.openlfdirstate(ui, repo)
1271 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1271 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1272 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1272 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1273 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1273 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1274 unknown, ignored, r.clean)
1274 unknown, ignored, r.clean)
1275 repo.status = overridestatus
1275 repo.status = overridestatus
1276 orig(ui, repo, *dirs, **opts)
1276 orig(ui, repo, *dirs, **opts)
1277 repo.status = oldstatus
1277 repo.status = oldstatus
1278
1278
1279 def overriderollback(orig, ui, repo, **opts):
1279 def overriderollback(orig, ui, repo, **opts):
1280 with repo.wlock():
1280 with repo.wlock():
1281 before = repo.dirstate.parents()
1281 before = repo.dirstate.parents()
1282 orphans = set(f for f in repo.dirstate
1282 orphans = set(f for f in repo.dirstate
1283 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1283 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1284 result = orig(ui, repo, **opts)
1284 result = orig(ui, repo, **opts)
1285 after = repo.dirstate.parents()
1285 after = repo.dirstate.parents()
1286 if before == after:
1286 if before == after:
1287 return result # no need to restore standins
1287 return result # no need to restore standins
1288
1288
1289 pctx = repo['.']
1289 pctx = repo['.']
1290 for f in repo.dirstate:
1290 for f in repo.dirstate:
1291 if lfutil.isstandin(f):
1291 if lfutil.isstandin(f):
1292 orphans.discard(f)
1292 orphans.discard(f)
1293 if repo.dirstate[f] == 'r':
1293 if repo.dirstate[f] == 'r':
1294 repo.wvfs.unlinkpath(f, ignoremissing=True)
1294 repo.wvfs.unlinkpath(f, ignoremissing=True)
1295 elif f in pctx:
1295 elif f in pctx:
1296 fctx = pctx[f]
1296 fctx = pctx[f]
1297 repo.wwrite(f, fctx.data(), fctx.flags())
1297 repo.wwrite(f, fctx.data(), fctx.flags())
1298 else:
1298 else:
1299 # content of standin is not so important in 'a',
1299 # content of standin is not so important in 'a',
1300 # 'm' or 'n' (coming from the 2nd parent) cases
1300 # 'm' or 'n' (coming from the 2nd parent) cases
1301 lfutil.writestandin(repo, f, '', False)
1301 lfutil.writestandin(repo, f, '', False)
1302 for standin in orphans:
1302 for standin in orphans:
1303 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1303 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1304
1304
1305 lfdirstate = lfutil.openlfdirstate(ui, repo)
1305 lfdirstate = lfutil.openlfdirstate(ui, repo)
1306 orphans = set(lfdirstate)
1306 orphans = set(lfdirstate)
1307 lfiles = lfutil.listlfiles(repo)
1307 lfiles = lfutil.listlfiles(repo)
1308 for file in lfiles:
1308 for file in lfiles:
1309 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1309 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1310 orphans.discard(file)
1310 orphans.discard(file)
1311 for lfile in orphans:
1311 for lfile in orphans:
1312 lfdirstate.drop(lfile)
1312 lfdirstate.drop(lfile)
1313 lfdirstate.write()
1313 lfdirstate.write()
1314 return result
1314 return result
1315
1315
1316 def overridetransplant(orig, ui, repo, *revs, **opts):
1316 def overridetransplant(orig, ui, repo, *revs, **opts):
1317 resuming = opts.get(r'continue')
1317 resuming = opts.get(r'continue')
1318 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1318 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1319 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1319 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1320 try:
1320 try:
1321 result = orig(ui, repo, *revs, **opts)
1321 result = orig(ui, repo, *revs, **opts)
1322 finally:
1322 finally:
1323 repo._lfstatuswriters.pop()
1323 repo._lfstatuswriters.pop()
1324 repo._lfcommithooks.pop()
1324 repo._lfcommithooks.pop()
1325 return result
1325 return result
1326
1326
1327 def overridecat(orig, ui, repo, file1, *pats, **opts):
1327 def overridecat(orig, ui, repo, file1, *pats, **opts):
1328 opts = pycompat.byteskwargs(opts)
1328 opts = pycompat.byteskwargs(opts)
1329 ctx = scmutil.revsingle(repo, opts.get('rev'))
1329 ctx = scmutil.revsingle(repo, opts.get('rev'))
1330 err = 1
1330 err = 1
1331 notbad = set()
1331 notbad = set()
1332 m = scmutil.match(ctx, (file1,) + pats, opts)
1332 m = scmutil.match(ctx, (file1,) + pats, opts)
1333 origmatchfn = m.matchfn
1333 origmatchfn = m.matchfn
1334 def lfmatchfn(f):
1334 def lfmatchfn(f):
1335 if origmatchfn(f):
1335 if origmatchfn(f):
1336 return True
1336 return True
1337 lf = lfutil.splitstandin(f)
1337 lf = lfutil.splitstandin(f)
1338 if lf is None:
1338 if lf is None:
1339 return False
1339 return False
1340 notbad.add(lf)
1340 notbad.add(lf)
1341 return origmatchfn(lf)
1341 return origmatchfn(lf)
1342 m.matchfn = lfmatchfn
1342 m.matchfn = lfmatchfn
1343 origbadfn = m.bad
1343 origbadfn = m.bad
1344 def lfbadfn(f, msg):
1344 def lfbadfn(f, msg):
1345 if not f in notbad:
1345 if not f in notbad:
1346 origbadfn(f, msg)
1346 origbadfn(f, msg)
1347 m.bad = lfbadfn
1347 m.bad = lfbadfn
1348
1348
1349 origvisitdirfn = m.visitdir
1349 origvisitdirfn = m.visitdir
1350 def lfvisitdirfn(dir):
1350 def lfvisitdirfn(dir):
1351 if dir == lfutil.shortname:
1351 if dir == lfutil.shortname:
1352 return True
1352 return True
1353 ret = origvisitdirfn(dir)
1353 ret = origvisitdirfn(dir)
1354 if ret:
1354 if ret:
1355 return ret
1355 return ret
1356 lf = lfutil.splitstandin(dir)
1356 lf = lfutil.splitstandin(dir)
1357 if lf is None:
1357 if lf is None:
1358 return False
1358 return False
1359 return origvisitdirfn(lf)
1359 return origvisitdirfn(lf)
1360 m.visitdir = lfvisitdirfn
1360 m.visitdir = lfvisitdirfn
1361
1361
1362 for f in ctx.walk(m):
1362 for f in ctx.walk(m):
1363 with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp:
1363 with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp:
1364 lf = lfutil.splitstandin(f)
1364 lf = lfutil.splitstandin(f)
1365 if lf is None or origmatchfn(f):
1365 if lf is None or origmatchfn(f):
1366 # duplicating unreachable code from commands.cat
1366 # duplicating unreachable code from commands.cat
1367 data = ctx[f].data()
1367 data = ctx[f].data()
1368 if opts.get('decode'):
1368 if opts.get('decode'):
1369 data = repo.wwritedata(f, data)
1369 data = repo.wwritedata(f, data)
1370 fp.write(data)
1370 fp.write(data)
1371 else:
1371 else:
1372 hash = lfutil.readasstandin(ctx[f])
1372 hash = lfutil.readasstandin(ctx[f])
1373 if not lfutil.inusercache(repo.ui, hash):
1373 if not lfutil.inusercache(repo.ui, hash):
1374 store = storefactory.openstore(repo)
1374 store = storefactory.openstore(repo)
1375 success, missing = store.get([(lf, hash)])
1375 success, missing = store.get([(lf, hash)])
1376 if len(success) != 1:
1376 if len(success) != 1:
1377 raise error.Abort(
1377 raise error.Abort(
1378 _('largefile %s is not in cache and could not be '
1378 _('largefile %s is not in cache and could not be '
1379 'downloaded') % lf)
1379 'downloaded') % lf)
1380 path = lfutil.usercachepath(repo.ui, hash)
1380 path = lfutil.usercachepath(repo.ui, hash)
1381 with open(path, "rb") as fpin:
1381 with open(path, "rb") as fpin:
1382 for chunk in util.filechunkiter(fpin):
1382 for chunk in util.filechunkiter(fpin):
1383 fp.write(chunk)
1383 fp.write(chunk)
1384 err = 0
1384 err = 0
1385 return err
1385 return err
1386
1386
1387 def mergeupdate(orig, repo, node, branchmerge, force,
1387 def mergeupdate(orig, repo, node, branchmerge, force,
1388 *args, **kwargs):
1388 *args, **kwargs):
1389 matcher = kwargs.get(r'matcher', None)
1389 matcher = kwargs.get(r'matcher', None)
1390 # note if this is a partial update
1390 # note if this is a partial update
1391 partial = matcher and not matcher.always()
1391 partial = matcher and not matcher.always()
1392 with repo.wlock():
1392 with repo.wlock():
1393 # branch | | |
1393 # branch | | |
1394 # merge | force | partial | action
1394 # merge | force | partial | action
1395 # -------+-------+---------+--------------
1395 # -------+-------+---------+--------------
1396 # x | x | x | linear-merge
1396 # x | x | x | linear-merge
1397 # o | x | x | branch-merge
1397 # o | x | x | branch-merge
1398 # x | o | x | overwrite (as clean update)
1398 # x | o | x | overwrite (as clean update)
1399 # o | o | x | force-branch-merge (*1)
1399 # o | o | x | force-branch-merge (*1)
1400 # x | x | o | (*)
1400 # x | x | o | (*)
1401 # o | x | o | (*)
1401 # o | x | o | (*)
1402 # x | o | o | overwrite (as revert)
1402 # x | o | o | overwrite (as revert)
1403 # o | o | o | (*)
1403 # o | o | o | (*)
1404 #
1404 #
1405 # (*) don't care
1405 # (*) don't care
1406 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1406 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1407
1407
1408 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1408 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1409 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1409 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1410 repo.getcwd()),
1410 repo.getcwd()),
1411 subrepos=[], ignored=False,
1411 subrepos=[], ignored=False,
1412 clean=True, unknown=False)
1412 clean=True, unknown=False)
1413 oldclean = set(s.clean)
1413 oldclean = set(s.clean)
1414 pctx = repo['.']
1414 pctx = repo['.']
1415 dctx = repo[node]
1415 dctx = repo[node]
1416 for lfile in unsure + s.modified:
1416 for lfile in unsure + s.modified:
1417 lfileabs = repo.wvfs.join(lfile)
1417 lfileabs = repo.wvfs.join(lfile)
1418 if not repo.wvfs.exists(lfileabs):
1418 if not repo.wvfs.exists(lfileabs):
1419 continue
1419 continue
1420 lfhash = lfutil.hashfile(lfileabs)
1420 lfhash = lfutil.hashfile(lfileabs)
1421 standin = lfutil.standin(lfile)
1421 standin = lfutil.standin(lfile)
1422 lfutil.writestandin(repo, standin, lfhash,
1422 lfutil.writestandin(repo, standin, lfhash,
1423 lfutil.getexecutable(lfileabs))
1423 lfutil.getexecutable(lfileabs))
1424 if (standin in pctx and
1424 if (standin in pctx and
1425 lfhash == lfutil.readasstandin(pctx[standin])):
1425 lfhash == lfutil.readasstandin(pctx[standin])):
1426 oldclean.add(lfile)
1426 oldclean.add(lfile)
1427 for lfile in s.added:
1427 for lfile in s.added:
1428 fstandin = lfutil.standin(lfile)
1428 fstandin = lfutil.standin(lfile)
1429 if fstandin not in dctx:
1429 if fstandin not in dctx:
1430 # in this case, content of standin file is meaningless
1430 # in this case, content of standin file is meaningless
1431 # (in dctx, lfile is unknown, or normal file)
1431 # (in dctx, lfile is unknown, or normal file)
1432 continue
1432 continue
1433 lfutil.updatestandin(repo, lfile, fstandin)
1433 lfutil.updatestandin(repo, lfile, fstandin)
1434 # mark all clean largefiles as dirty, just in case the update gets
1434 # mark all clean largefiles as dirty, just in case the update gets
1435 # interrupted before largefiles and lfdirstate are synchronized
1435 # interrupted before largefiles and lfdirstate are synchronized
1436 for lfile in oldclean:
1436 for lfile in oldclean:
1437 lfdirstate.normallookup(lfile)
1437 lfdirstate.normallookup(lfile)
1438 lfdirstate.write()
1438 lfdirstate.write()
1439
1439
1440 oldstandins = lfutil.getstandinsstate(repo)
1440 oldstandins = lfutil.getstandinsstate(repo)
1441 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1441 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1442 # good candidate for in-memory merge (large files, custom dirstate,
1442 # good candidate for in-memory merge (large files, custom dirstate,
1443 # matcher usage).
1443 # matcher usage).
1444 kwargs[r'wc'] = repo[None]
1444 kwargs[r'wc'] = repo[None]
1445 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1445 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1446
1446
1447 newstandins = lfutil.getstandinsstate(repo)
1447 newstandins = lfutil.getstandinsstate(repo)
1448 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1448 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1449
1449
1450 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1450 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1451 # all the ones that didn't change as clean
1451 # all the ones that didn't change as clean
1452 for lfile in oldclean.difference(filelist):
1452 for lfile in oldclean.difference(filelist):
1453 lfdirstate.normal(lfile)
1453 lfdirstate.normal(lfile)
1454 lfdirstate.write()
1454 lfdirstate.write()
1455
1455
1456 if branchmerge or force or partial:
1456 if branchmerge or force or partial:
1457 filelist.extend(s.deleted + s.removed)
1457 filelist.extend(s.deleted + s.removed)
1458
1458
1459 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1459 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1460 normallookup=partial)
1460 normallookup=partial)
1461
1461
1462 return result
1462 return result
1463
1463
1464 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1464 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1465 result = orig(repo, files, *args, **kwargs)
1465 result = orig(repo, files, *args, **kwargs)
1466
1466
1467 filelist = []
1467 filelist = []
1468 for f in files:
1468 for f in files:
1469 lf = lfutil.splitstandin(f)
1469 lf = lfutil.splitstandin(f)
1470 if lf is not None:
1470 if lf is not None:
1471 filelist.append(lf)
1471 filelist.append(lf)
1472 if filelist:
1472 if filelist:
1473 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1473 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1474 printmessage=False, normallookup=True)
1474 printmessage=False, normallookup=True)
1475
1475
1476 return result
1476 return result
1477
1477
1478 def upgraderequirements(orig, repo):
1478 def upgraderequirements(orig, repo):
1479 reqs = orig(repo)
1479 reqs = orig(repo)
1480 if 'largefiles' in repo.requirements:
1480 if 'largefiles' in repo.requirements:
1481 reqs.add('largefiles')
1481 reqs.add('largefiles')
1482 return reqs
1482 return reqs
1483
1483
1484 _lfscheme = 'largefile://'
1484 _lfscheme = 'largefile://'
1485 def openlargefile(orig, ui, url_, data=None):
1485 def openlargefile(orig, ui, url_, data=None):
1486 if url_.startswith(_lfscheme):
1486 if url_.startswith(_lfscheme):
1487 if data:
1487 if data:
1488 msg = "cannot use data on a 'largefile://' url"
1488 msg = "cannot use data on a 'largefile://' url"
1489 raise error.ProgrammingError(msg)
1489 raise error.ProgrammingError(msg)
1490 lfid = url_[len(_lfscheme):]
1490 lfid = url_[len(_lfscheme):]
1491 return storefactory.getlfile(ui, lfid)
1491 return storefactory.getlfile(ui, lfid)
1492 else:
1492 else:
1493 return orig(ui, url_, data=data)
1493 return orig(ui, url_, data=data)
@@ -1,3211 +1,3213 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13 import tempfile
13 import tempfile
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22
22
23 from . import (
23 from . import (
24 bookmarks,
24 bookmarks,
25 changelog,
25 changelog,
26 copies,
26 copies,
27 crecord as crecordmod,
27 crecord as crecordmod,
28 dirstateguard,
28 dirstateguard,
29 encoding,
29 encoding,
30 error,
30 error,
31 formatter,
31 formatter,
32 logcmdutil,
32 logcmdutil,
33 match as matchmod,
33 match as matchmod,
34 merge as mergemod,
34 merge as mergemod,
35 mergeutil,
35 mergeutil,
36 obsolete,
36 obsolete,
37 patch,
37 patch,
38 pathutil,
38 pathutil,
39 pycompat,
39 pycompat,
40 registrar,
40 registrar,
41 revlog,
41 revlog,
42 rewriteutil,
42 rewriteutil,
43 scmutil,
43 scmutil,
44 smartset,
44 smartset,
45 subrepoutil,
45 subrepoutil,
46 templatekw,
46 templatekw,
47 templater,
47 templater,
48 util,
48 util,
49 vfs as vfsmod,
49 vfs as vfsmod,
50 )
50 )
51
51
52 from .utils import (
52 from .utils import (
53 dateutil,
53 dateutil,
54 stringutil,
54 stringutil,
55 )
55 )
56
56
57 stringio = util.stringio
57 stringio = util.stringio
58
58
59 # templates of common command options
59 # templates of common command options
60
60
61 dryrunopts = [
61 dryrunopts = [
62 ('n', 'dry-run', None,
62 ('n', 'dry-run', None,
63 _('do not perform actions, just print output')),
63 _('do not perform actions, just print output')),
64 ]
64 ]
65
65
66 remoteopts = [
66 remoteopts = [
67 ('e', 'ssh', '',
67 ('e', 'ssh', '',
68 _('specify ssh command to use'), _('CMD')),
68 _('specify ssh command to use'), _('CMD')),
69 ('', 'remotecmd', '',
69 ('', 'remotecmd', '',
70 _('specify hg command to run on the remote side'), _('CMD')),
70 _('specify hg command to run on the remote side'), _('CMD')),
71 ('', 'insecure', None,
71 ('', 'insecure', None,
72 _('do not verify server certificate (ignoring web.cacerts config)')),
72 _('do not verify server certificate (ignoring web.cacerts config)')),
73 ]
73 ]
74
74
75 walkopts = [
75 walkopts = [
76 ('I', 'include', [],
76 ('I', 'include', [],
77 _('include names matching the given patterns'), _('PATTERN')),
77 _('include names matching the given patterns'), _('PATTERN')),
78 ('X', 'exclude', [],
78 ('X', 'exclude', [],
79 _('exclude names matching the given patterns'), _('PATTERN')),
79 _('exclude names matching the given patterns'), _('PATTERN')),
80 ]
80 ]
81
81
82 commitopts = [
82 commitopts = [
83 ('m', 'message', '',
83 ('m', 'message', '',
84 _('use text as commit message'), _('TEXT')),
84 _('use text as commit message'), _('TEXT')),
85 ('l', 'logfile', '',
85 ('l', 'logfile', '',
86 _('read commit message from file'), _('FILE')),
86 _('read commit message from file'), _('FILE')),
87 ]
87 ]
88
88
89 commitopts2 = [
89 commitopts2 = [
90 ('d', 'date', '',
90 ('d', 'date', '',
91 _('record the specified date as commit date'), _('DATE')),
91 _('record the specified date as commit date'), _('DATE')),
92 ('u', 'user', '',
92 ('u', 'user', '',
93 _('record the specified user as committer'), _('USER')),
93 _('record the specified user as committer'), _('USER')),
94 ]
94 ]
95
95
96 # hidden for now
96 # hidden for now
97 formatteropts = [
97 formatteropts = [
98 ('T', 'template', '',
98 ('T', 'template', '',
99 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
99 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
100 ]
100 ]
101
101
102 templateopts = [
102 templateopts = [
103 ('', 'style', '',
103 ('', 'style', '',
104 _('display using template map file (DEPRECATED)'), _('STYLE')),
104 _('display using template map file (DEPRECATED)'), _('STYLE')),
105 ('T', 'template', '',
105 ('T', 'template', '',
106 _('display with template'), _('TEMPLATE')),
106 _('display with template'), _('TEMPLATE')),
107 ]
107 ]
108
108
109 logopts = [
109 logopts = [
110 ('p', 'patch', None, _('show patch')),
110 ('p', 'patch', None, _('show patch')),
111 ('g', 'git', None, _('use git extended diff format')),
111 ('g', 'git', None, _('use git extended diff format')),
112 ('l', 'limit', '',
112 ('l', 'limit', '',
113 _('limit number of changes displayed'), _('NUM')),
113 _('limit number of changes displayed'), _('NUM')),
114 ('M', 'no-merges', None, _('do not show merges')),
114 ('M', 'no-merges', None, _('do not show merges')),
115 ('', 'stat', None, _('output diffstat-style summary of changes')),
115 ('', 'stat', None, _('output diffstat-style summary of changes')),
116 ('G', 'graph', None, _("show the revision DAG")),
116 ('G', 'graph', None, _("show the revision DAG")),
117 ] + templateopts
117 ] + templateopts
118
118
119 diffopts = [
119 diffopts = [
120 ('a', 'text', None, _('treat all files as text')),
120 ('a', 'text', None, _('treat all files as text')),
121 ('g', 'git', None, _('use git extended diff format')),
121 ('g', 'git', None, _('use git extended diff format')),
122 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
122 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
123 ('', 'nodates', None, _('omit dates from diff headers'))
123 ('', 'nodates', None, _('omit dates from diff headers'))
124 ]
124 ]
125
125
126 diffwsopts = [
126 diffwsopts = [
127 ('w', 'ignore-all-space', None,
127 ('w', 'ignore-all-space', None,
128 _('ignore white space when comparing lines')),
128 _('ignore white space when comparing lines')),
129 ('b', 'ignore-space-change', None,
129 ('b', 'ignore-space-change', None,
130 _('ignore changes in the amount of white space')),
130 _('ignore changes in the amount of white space')),
131 ('B', 'ignore-blank-lines', None,
131 ('B', 'ignore-blank-lines', None,
132 _('ignore changes whose lines are all blank')),
132 _('ignore changes whose lines are all blank')),
133 ('Z', 'ignore-space-at-eol', None,
133 ('Z', 'ignore-space-at-eol', None,
134 _('ignore changes in whitespace at EOL')),
134 _('ignore changes in whitespace at EOL')),
135 ]
135 ]
136
136
137 diffopts2 = [
137 diffopts2 = [
138 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
138 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
139 ('p', 'show-function', None, _('show which function each change is in')),
139 ('p', 'show-function', None, _('show which function each change is in')),
140 ('', 'reverse', None, _('produce a diff that undoes the changes')),
140 ('', 'reverse', None, _('produce a diff that undoes the changes')),
141 ] + diffwsopts + [
141 ] + diffwsopts + [
142 ('U', 'unified', '',
142 ('U', 'unified', '',
143 _('number of lines of context to show'), _('NUM')),
143 _('number of lines of context to show'), _('NUM')),
144 ('', 'stat', None, _('output diffstat-style summary of changes')),
144 ('', 'stat', None, _('output diffstat-style summary of changes')),
145 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
145 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
146 ]
146 ]
147
147
148 mergetoolopts = [
148 mergetoolopts = [
149 ('t', 'tool', '', _('specify merge tool')),
149 ('t', 'tool', '', _('specify merge tool')),
150 ]
150 ]
151
151
152 similarityopts = [
152 similarityopts = [
153 ('s', 'similarity', '',
153 ('s', 'similarity', '',
154 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
154 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
155 ]
155 ]
156
156
157 subrepoopts = [
157 subrepoopts = [
158 ('S', 'subrepos', None,
158 ('S', 'subrepos', None,
159 _('recurse into subrepositories'))
159 _('recurse into subrepositories'))
160 ]
160 ]
161
161
162 debugrevlogopts = [
162 debugrevlogopts = [
163 ('c', 'changelog', False, _('open changelog')),
163 ('c', 'changelog', False, _('open changelog')),
164 ('m', 'manifest', False, _('open manifest')),
164 ('m', 'manifest', False, _('open manifest')),
165 ('', 'dir', '', _('open directory manifest')),
165 ('', 'dir', '', _('open directory manifest')),
166 ]
166 ]
167
167
168 # special string such that everything below this line will be ingored in the
168 # special string such that everything below this line will be ingored in the
169 # editor text
169 # editor text
170 _linebelow = "^HG: ------------------------ >8 ------------------------$"
170 _linebelow = "^HG: ------------------------ >8 ------------------------$"
171
171
172 def ishunk(x):
172 def ishunk(x):
173 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
173 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
174 return isinstance(x, hunkclasses)
174 return isinstance(x, hunkclasses)
175
175
176 def newandmodified(chunks, originalchunks):
176 def newandmodified(chunks, originalchunks):
177 newlyaddedandmodifiedfiles = set()
177 newlyaddedandmodifiedfiles = set()
178 for chunk in chunks:
178 for chunk in chunks:
179 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
179 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
180 originalchunks:
180 originalchunks:
181 newlyaddedandmodifiedfiles.add(chunk.header.filename())
181 newlyaddedandmodifiedfiles.add(chunk.header.filename())
182 return newlyaddedandmodifiedfiles
182 return newlyaddedandmodifiedfiles
183
183
184 def parsealiases(cmd):
184 def parsealiases(cmd):
185 return cmd.lstrip("^").split("|")
185 return cmd.lstrip("^").split("|")
186
186
187 def setupwrapcolorwrite(ui):
187 def setupwrapcolorwrite(ui):
188 # wrap ui.write so diff output can be labeled/colorized
188 # wrap ui.write so diff output can be labeled/colorized
189 def wrapwrite(orig, *args, **kw):
189 def wrapwrite(orig, *args, **kw):
190 label = kw.pop(r'label', '')
190 label = kw.pop(r'label', '')
191 for chunk, l in patch.difflabel(lambda: args):
191 for chunk, l in patch.difflabel(lambda: args):
192 orig(chunk, label=label + l)
192 orig(chunk, label=label + l)
193
193
194 oldwrite = ui.write
194 oldwrite = ui.write
195 def wrap(*args, **kwargs):
195 def wrap(*args, **kwargs):
196 return wrapwrite(oldwrite, *args, **kwargs)
196 return wrapwrite(oldwrite, *args, **kwargs)
197 setattr(ui, 'write', wrap)
197 setattr(ui, 'write', wrap)
198 return oldwrite
198 return oldwrite
199
199
200 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
200 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
201 if usecurses:
201 if usecurses:
202 if testfile:
202 if testfile:
203 recordfn = crecordmod.testdecorator(testfile,
203 recordfn = crecordmod.testdecorator(testfile,
204 crecordmod.testchunkselector)
204 crecordmod.testchunkselector)
205 else:
205 else:
206 recordfn = crecordmod.chunkselector
206 recordfn = crecordmod.chunkselector
207
207
208 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
208 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
209
209
210 else:
210 else:
211 return patch.filterpatch(ui, originalhunks, operation)
211 return patch.filterpatch(ui, originalhunks, operation)
212
212
213 def recordfilter(ui, originalhunks, operation=None):
213 def recordfilter(ui, originalhunks, operation=None):
214 """ Prompts the user to filter the originalhunks and return a list of
214 """ Prompts the user to filter the originalhunks and return a list of
215 selected hunks.
215 selected hunks.
216 *operation* is used for to build ui messages to indicate the user what
216 *operation* is used for to build ui messages to indicate the user what
217 kind of filtering they are doing: reverting, committing, shelving, etc.
217 kind of filtering they are doing: reverting, committing, shelving, etc.
218 (see patch.filterpatch).
218 (see patch.filterpatch).
219 """
219 """
220 usecurses = crecordmod.checkcurses(ui)
220 usecurses = crecordmod.checkcurses(ui)
221 testfile = ui.config('experimental', 'crecordtest')
221 testfile = ui.config('experimental', 'crecordtest')
222 oldwrite = setupwrapcolorwrite(ui)
222 oldwrite = setupwrapcolorwrite(ui)
223 try:
223 try:
224 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
224 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
225 testfile, operation)
225 testfile, operation)
226 finally:
226 finally:
227 ui.write = oldwrite
227 ui.write = oldwrite
228 return newchunks, newopts
228 return newchunks, newopts
229
229
230 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
230 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
231 filterfn, *pats, **opts):
231 filterfn, *pats, **opts):
232 opts = pycompat.byteskwargs(opts)
232 opts = pycompat.byteskwargs(opts)
233 if not ui.interactive():
233 if not ui.interactive():
234 if cmdsuggest:
234 if cmdsuggest:
235 msg = _('running non-interactively, use %s instead') % cmdsuggest
235 msg = _('running non-interactively, use %s instead') % cmdsuggest
236 else:
236 else:
237 msg = _('running non-interactively')
237 msg = _('running non-interactively')
238 raise error.Abort(msg)
238 raise error.Abort(msg)
239
239
240 # make sure username is set before going interactive
240 # make sure username is set before going interactive
241 if not opts.get('user'):
241 if not opts.get('user'):
242 ui.username() # raise exception, username not provided
242 ui.username() # raise exception, username not provided
243
243
244 def recordfunc(ui, repo, message, match, opts):
244 def recordfunc(ui, repo, message, match, opts):
245 """This is generic record driver.
245 """This is generic record driver.
246
246
247 Its job is to interactively filter local changes, and
247 Its job is to interactively filter local changes, and
248 accordingly prepare working directory into a state in which the
248 accordingly prepare working directory into a state in which the
249 job can be delegated to a non-interactive commit command such as
249 job can be delegated to a non-interactive commit command such as
250 'commit' or 'qrefresh'.
250 'commit' or 'qrefresh'.
251
251
252 After the actual job is done by non-interactive command, the
252 After the actual job is done by non-interactive command, the
253 working directory is restored to its original state.
253 working directory is restored to its original state.
254
254
255 In the end we'll record interesting changes, and everything else
255 In the end we'll record interesting changes, and everything else
256 will be left in place, so the user can continue working.
256 will be left in place, so the user can continue working.
257 """
257 """
258
258
259 checkunfinished(repo, commit=True)
259 checkunfinished(repo, commit=True)
260 wctx = repo[None]
260 wctx = repo[None]
261 merge = len(wctx.parents()) > 1
261 merge = len(wctx.parents()) > 1
262 if merge:
262 if merge:
263 raise error.Abort(_('cannot partially commit a merge '
263 raise error.Abort(_('cannot partially commit a merge '
264 '(use "hg commit" instead)'))
264 '(use "hg commit" instead)'))
265
265
266 def fail(f, msg):
266 def fail(f, msg):
267 raise error.Abort('%s: %s' % (f, msg))
267 raise error.Abort('%s: %s' % (f, msg))
268
268
269 force = opts.get('force')
269 force = opts.get('force')
270 if not force:
270 if not force:
271 vdirs = []
271 vdirs = []
272 match.explicitdir = vdirs.append
272 match.explicitdir = vdirs.append
273 match.bad = fail
273 match.bad = fail
274
274
275 status = repo.status(match=match)
275 status = repo.status(match=match)
276 if not force:
276 if not force:
277 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
277 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
278 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
278 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
279 diffopts.nodates = True
279 diffopts.nodates = True
280 diffopts.git = True
280 diffopts.git = True
281 diffopts.showfunc = True
281 diffopts.showfunc = True
282 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
282 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
283 originalchunks = patch.parsepatch(originaldiff)
283 originalchunks = patch.parsepatch(originaldiff)
284
284
285 # 1. filter patch, since we are intending to apply subset of it
285 # 1. filter patch, since we are intending to apply subset of it
286 try:
286 try:
287 chunks, newopts = filterfn(ui, originalchunks)
287 chunks, newopts = filterfn(ui, originalchunks)
288 except error.PatchError as err:
288 except error.PatchError as err:
289 raise error.Abort(_('error parsing patch: %s') % err)
289 raise error.Abort(_('error parsing patch: %s') % err)
290 opts.update(newopts)
290 opts.update(newopts)
291
291
292 # We need to keep a backup of files that have been newly added and
292 # We need to keep a backup of files that have been newly added and
293 # modified during the recording process because there is a previous
293 # modified during the recording process because there is a previous
294 # version without the edit in the workdir
294 # version without the edit in the workdir
295 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
295 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
296 contenders = set()
296 contenders = set()
297 for h in chunks:
297 for h in chunks:
298 try:
298 try:
299 contenders.update(set(h.files()))
299 contenders.update(set(h.files()))
300 except AttributeError:
300 except AttributeError:
301 pass
301 pass
302
302
303 changed = status.modified + status.added + status.removed
303 changed = status.modified + status.added + status.removed
304 newfiles = [f for f in changed if f in contenders]
304 newfiles = [f for f in changed if f in contenders]
305 if not newfiles:
305 if not newfiles:
306 ui.status(_('no changes to record\n'))
306 ui.status(_('no changes to record\n'))
307 return 0
307 return 0
308
308
309 modified = set(status.modified)
309 modified = set(status.modified)
310
310
311 # 2. backup changed files, so we can restore them in the end
311 # 2. backup changed files, so we can restore them in the end
312
312
313 if backupall:
313 if backupall:
314 tobackup = changed
314 tobackup = changed
315 else:
315 else:
316 tobackup = [f for f in newfiles if f in modified or f in \
316 tobackup = [f for f in newfiles if f in modified or f in \
317 newlyaddedandmodifiedfiles]
317 newlyaddedandmodifiedfiles]
318 backups = {}
318 backups = {}
319 if tobackup:
319 if tobackup:
320 backupdir = repo.vfs.join('record-backups')
320 backupdir = repo.vfs.join('record-backups')
321 try:
321 try:
322 os.mkdir(backupdir)
322 os.mkdir(backupdir)
323 except OSError as err:
323 except OSError as err:
324 if err.errno != errno.EEXIST:
324 if err.errno != errno.EEXIST:
325 raise
325 raise
326 try:
326 try:
327 # backup continues
327 # backup continues
328 for f in tobackup:
328 for f in tobackup:
329 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
329 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
330 dir=backupdir)
330 dir=backupdir)
331 os.close(fd)
331 os.close(fd)
332 ui.debug('backup %r as %r\n' % (f, tmpname))
332 ui.debug('backup %r as %r\n' % (f, tmpname))
333 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
333 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
334 backups[f] = tmpname
334 backups[f] = tmpname
335
335
336 fp = stringio()
336 fp = stringio()
337 for c in chunks:
337 for c in chunks:
338 fname = c.filename()
338 fname = c.filename()
339 if fname in backups:
339 if fname in backups:
340 c.write(fp)
340 c.write(fp)
341 dopatch = fp.tell()
341 dopatch = fp.tell()
342 fp.seek(0)
342 fp.seek(0)
343
343
344 # 2.5 optionally review / modify patch in text editor
344 # 2.5 optionally review / modify patch in text editor
345 if opts.get('review', False):
345 if opts.get('review', False):
346 patchtext = (crecordmod.diffhelptext
346 patchtext = (crecordmod.diffhelptext
347 + crecordmod.patchhelptext
347 + crecordmod.patchhelptext
348 + fp.read())
348 + fp.read())
349 reviewedpatch = ui.edit(patchtext, "",
349 reviewedpatch = ui.edit(patchtext, "",
350 action="diff",
350 action="diff",
351 repopath=repo.path)
351 repopath=repo.path)
352 fp.truncate(0)
352 fp.truncate(0)
353 fp.write(reviewedpatch)
353 fp.write(reviewedpatch)
354 fp.seek(0)
354 fp.seek(0)
355
355
356 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
356 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
357 # 3a. apply filtered patch to clean repo (clean)
357 # 3a. apply filtered patch to clean repo (clean)
358 if backups:
358 if backups:
359 # Equivalent to hg.revert
359 # Equivalent to hg.revert
360 m = scmutil.matchfiles(repo, backups.keys())
360 m = scmutil.matchfiles(repo, backups.keys())
361 mergemod.update(repo, repo.dirstate.p1(),
361 mergemod.update(repo, repo.dirstate.p1(),
362 False, True, matcher=m)
362 False, True, matcher=m)
363
363
364 # 3b. (apply)
364 # 3b. (apply)
365 if dopatch:
365 if dopatch:
366 try:
366 try:
367 ui.debug('applying patch\n')
367 ui.debug('applying patch\n')
368 ui.debug(fp.getvalue())
368 ui.debug(fp.getvalue())
369 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
369 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
370 except error.PatchError as err:
370 except error.PatchError as err:
371 raise error.Abort(pycompat.bytestr(err))
371 raise error.Abort(pycompat.bytestr(err))
372 del fp
372 del fp
373
373
374 # 4. We prepared working directory according to filtered
374 # 4. We prepared working directory according to filtered
375 # patch. Now is the time to delegate the job to
375 # patch. Now is the time to delegate the job to
376 # commit/qrefresh or the like!
376 # commit/qrefresh or the like!
377
377
378 # Make all of the pathnames absolute.
378 # Make all of the pathnames absolute.
379 newfiles = [repo.wjoin(nf) for nf in newfiles]
379 newfiles = [repo.wjoin(nf) for nf in newfiles]
380 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
380 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
381 finally:
381 finally:
382 # 5. finally restore backed-up files
382 # 5. finally restore backed-up files
383 try:
383 try:
384 dirstate = repo.dirstate
384 dirstate = repo.dirstate
385 for realname, tmpname in backups.iteritems():
385 for realname, tmpname in backups.iteritems():
386 ui.debug('restoring %r to %r\n' % (tmpname, realname))
386 ui.debug('restoring %r to %r\n' % (tmpname, realname))
387
387
388 if dirstate[realname] == 'n':
388 if dirstate[realname] == 'n':
389 # without normallookup, restoring timestamp
389 # without normallookup, restoring timestamp
390 # may cause partially committed files
390 # may cause partially committed files
391 # to be treated as unmodified
391 # to be treated as unmodified
392 dirstate.normallookup(realname)
392 dirstate.normallookup(realname)
393
393
394 # copystat=True here and above are a hack to trick any
394 # copystat=True here and above are a hack to trick any
395 # editors that have f open that we haven't modified them.
395 # editors that have f open that we haven't modified them.
396 #
396 #
397 # Also note that this racy as an editor could notice the
397 # Also note that this racy as an editor could notice the
398 # file's mtime before we've finished writing it.
398 # file's mtime before we've finished writing it.
399 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
399 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
400 os.unlink(tmpname)
400 os.unlink(tmpname)
401 if tobackup:
401 if tobackup:
402 os.rmdir(backupdir)
402 os.rmdir(backupdir)
403 except OSError:
403 except OSError:
404 pass
404 pass
405
405
406 def recordinwlock(ui, repo, message, match, opts):
406 def recordinwlock(ui, repo, message, match, opts):
407 with repo.wlock():
407 with repo.wlock():
408 return recordfunc(ui, repo, message, match, opts)
408 return recordfunc(ui, repo, message, match, opts)
409
409
410 return commit(ui, repo, recordinwlock, pats, opts)
410 return commit(ui, repo, recordinwlock, pats, opts)
411
411
412 class dirnode(object):
412 class dirnode(object):
413 """
413 """
414 Represent a directory in user working copy with information required for
414 Represent a directory in user working copy with information required for
415 the purpose of tersing its status.
415 the purpose of tersing its status.
416
416
417 path is the path to the directory
417 path is the path to the directory
418
418
419 statuses is a set of statuses of all files in this directory (this includes
419 statuses is a set of statuses of all files in this directory (this includes
420 all the files in all the subdirectories too)
420 all the files in all the subdirectories too)
421
421
422 files is a list of files which are direct child of this directory
422 files is a list of files which are direct child of this directory
423
423
424 subdirs is a dictionary of sub-directory name as the key and it's own
424 subdirs is a dictionary of sub-directory name as the key and it's own
425 dirnode object as the value
425 dirnode object as the value
426 """
426 """
427
427
428 def __init__(self, dirpath):
428 def __init__(self, dirpath):
429 self.path = dirpath
429 self.path = dirpath
430 self.statuses = set([])
430 self.statuses = set([])
431 self.files = []
431 self.files = []
432 self.subdirs = {}
432 self.subdirs = {}
433
433
434 def _addfileindir(self, filename, status):
434 def _addfileindir(self, filename, status):
435 """Add a file in this directory as a direct child."""
435 """Add a file in this directory as a direct child."""
436 self.files.append((filename, status))
436 self.files.append((filename, status))
437
437
438 def addfile(self, filename, status):
438 def addfile(self, filename, status):
439 """
439 """
440 Add a file to this directory or to its direct parent directory.
440 Add a file to this directory or to its direct parent directory.
441
441
442 If the file is not direct child of this directory, we traverse to the
442 If the file is not direct child of this directory, we traverse to the
443 directory of which this file is a direct child of and add the file
443 directory of which this file is a direct child of and add the file
444 there.
444 there.
445 """
445 """
446
446
447 # the filename contains a path separator, it means it's not the direct
447 # the filename contains a path separator, it means it's not the direct
448 # child of this directory
448 # child of this directory
449 if '/' in filename:
449 if '/' in filename:
450 subdir, filep = filename.split('/', 1)
450 subdir, filep = filename.split('/', 1)
451
451
452 # does the dirnode object for subdir exists
452 # does the dirnode object for subdir exists
453 if subdir not in self.subdirs:
453 if subdir not in self.subdirs:
454 subdirpath = os.path.join(self.path, subdir)
454 subdirpath = os.path.join(self.path, subdir)
455 self.subdirs[subdir] = dirnode(subdirpath)
455 self.subdirs[subdir] = dirnode(subdirpath)
456
456
457 # try adding the file in subdir
457 # try adding the file in subdir
458 self.subdirs[subdir].addfile(filep, status)
458 self.subdirs[subdir].addfile(filep, status)
459
459
460 else:
460 else:
461 self._addfileindir(filename, status)
461 self._addfileindir(filename, status)
462
462
463 if status not in self.statuses:
463 if status not in self.statuses:
464 self.statuses.add(status)
464 self.statuses.add(status)
465
465
466 def iterfilepaths(self):
466 def iterfilepaths(self):
467 """Yield (status, path) for files directly under this directory."""
467 """Yield (status, path) for files directly under this directory."""
468 for f, st in self.files:
468 for f, st in self.files:
469 yield st, os.path.join(self.path, f)
469 yield st, os.path.join(self.path, f)
470
470
471 def tersewalk(self, terseargs):
471 def tersewalk(self, terseargs):
472 """
472 """
473 Yield (status, path) obtained by processing the status of this
473 Yield (status, path) obtained by processing the status of this
474 dirnode.
474 dirnode.
475
475
476 terseargs is the string of arguments passed by the user with `--terse`
476 terseargs is the string of arguments passed by the user with `--terse`
477 flag.
477 flag.
478
478
479 Following are the cases which can happen:
479 Following are the cases which can happen:
480
480
481 1) All the files in the directory (including all the files in its
481 1) All the files in the directory (including all the files in its
482 subdirectories) share the same status and the user has asked us to terse
482 subdirectories) share the same status and the user has asked us to terse
483 that status. -> yield (status, dirpath)
483 that status. -> yield (status, dirpath)
484
484
485 2) Otherwise, we do following:
485 2) Otherwise, we do following:
486
486
487 a) Yield (status, filepath) for all the files which are in this
487 a) Yield (status, filepath) for all the files which are in this
488 directory (only the ones in this directory, not the subdirs)
488 directory (only the ones in this directory, not the subdirs)
489
489
490 b) Recurse the function on all the subdirectories of this
490 b) Recurse the function on all the subdirectories of this
491 directory
491 directory
492 """
492 """
493
493
494 if len(self.statuses) == 1:
494 if len(self.statuses) == 1:
495 onlyst = self.statuses.pop()
495 onlyst = self.statuses.pop()
496
496
497 # Making sure we terse only when the status abbreviation is
497 # Making sure we terse only when the status abbreviation is
498 # passed as terse argument
498 # passed as terse argument
499 if onlyst in terseargs:
499 if onlyst in terseargs:
500 yield onlyst, self.path + pycompat.ossep
500 yield onlyst, self.path + pycompat.ossep
501 return
501 return
502
502
503 # add the files to status list
503 # add the files to status list
504 for st, fpath in self.iterfilepaths():
504 for st, fpath in self.iterfilepaths():
505 yield st, fpath
505 yield st, fpath
506
506
507 #recurse on the subdirs
507 #recurse on the subdirs
508 for dirobj in self.subdirs.values():
508 for dirobj in self.subdirs.values():
509 for st, fpath in dirobj.tersewalk(terseargs):
509 for st, fpath in dirobj.tersewalk(terseargs):
510 yield st, fpath
510 yield st, fpath
511
511
512 def tersedir(statuslist, terseargs):
512 def tersedir(statuslist, terseargs):
513 """
513 """
514 Terse the status if all the files in a directory shares the same status.
514 Terse the status if all the files in a directory shares the same status.
515
515
516 statuslist is scmutil.status() object which contains a list of files for
516 statuslist is scmutil.status() object which contains a list of files for
517 each status.
517 each status.
518 terseargs is string which is passed by the user as the argument to `--terse`
518 terseargs is string which is passed by the user as the argument to `--terse`
519 flag.
519 flag.
520
520
521 The function makes a tree of objects of dirnode class, and at each node it
521 The function makes a tree of objects of dirnode class, and at each node it
522 stores the information required to know whether we can terse a certain
522 stores the information required to know whether we can terse a certain
523 directory or not.
523 directory or not.
524 """
524 """
525 # the order matters here as that is used to produce final list
525 # the order matters here as that is used to produce final list
526 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
526 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
527
527
528 # checking the argument validity
528 # checking the argument validity
529 for s in pycompat.bytestr(terseargs):
529 for s in pycompat.bytestr(terseargs):
530 if s not in allst:
530 if s not in allst:
531 raise error.Abort(_("'%s' not recognized") % s)
531 raise error.Abort(_("'%s' not recognized") % s)
532
532
533 # creating a dirnode object for the root of the repo
533 # creating a dirnode object for the root of the repo
534 rootobj = dirnode('')
534 rootobj = dirnode('')
535 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
535 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
536 'ignored', 'removed')
536 'ignored', 'removed')
537
537
538 tersedict = {}
538 tersedict = {}
539 for attrname in pstatus:
539 for attrname in pstatus:
540 statuschar = attrname[0:1]
540 statuschar = attrname[0:1]
541 for f in getattr(statuslist, attrname):
541 for f in getattr(statuslist, attrname):
542 rootobj.addfile(f, statuschar)
542 rootobj.addfile(f, statuschar)
543 tersedict[statuschar] = []
543 tersedict[statuschar] = []
544
544
545 # we won't be tersing the root dir, so add files in it
545 # we won't be tersing the root dir, so add files in it
546 for st, fpath in rootobj.iterfilepaths():
546 for st, fpath in rootobj.iterfilepaths():
547 tersedict[st].append(fpath)
547 tersedict[st].append(fpath)
548
548
549 # process each sub-directory and build tersedict
549 # process each sub-directory and build tersedict
550 for subdir in rootobj.subdirs.values():
550 for subdir in rootobj.subdirs.values():
551 for st, f in subdir.tersewalk(terseargs):
551 for st, f in subdir.tersewalk(terseargs):
552 tersedict[st].append(f)
552 tersedict[st].append(f)
553
553
554 tersedlist = []
554 tersedlist = []
555 for st in allst:
555 for st in allst:
556 tersedict[st].sort()
556 tersedict[st].sort()
557 tersedlist.append(tersedict[st])
557 tersedlist.append(tersedict[st])
558
558
559 return tersedlist
559 return tersedlist
560
560
561 def _commentlines(raw):
561 def _commentlines(raw):
562 '''Surround lineswith a comment char and a new line'''
562 '''Surround lineswith a comment char and a new line'''
563 lines = raw.splitlines()
563 lines = raw.splitlines()
564 commentedlines = ['# %s' % line for line in lines]
564 commentedlines = ['# %s' % line for line in lines]
565 return '\n'.join(commentedlines) + '\n'
565 return '\n'.join(commentedlines) + '\n'
566
566
567 def _conflictsmsg(repo):
567 def _conflictsmsg(repo):
568 mergestate = mergemod.mergestate.read(repo)
568 mergestate = mergemod.mergestate.read(repo)
569 if not mergestate.active():
569 if not mergestate.active():
570 return
570 return
571
571
572 m = scmutil.match(repo[None])
572 m = scmutil.match(repo[None])
573 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
573 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
574 if unresolvedlist:
574 if unresolvedlist:
575 mergeliststr = '\n'.join(
575 mergeliststr = '\n'.join(
576 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
576 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
577 for path in unresolvedlist])
577 for path in unresolvedlist])
578 msg = _('''Unresolved merge conflicts:
578 msg = _('''Unresolved merge conflicts:
579
579
580 %s
580 %s
581
581
582 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
582 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
583 else:
583 else:
584 msg = _('No unresolved merge conflicts.')
584 msg = _('No unresolved merge conflicts.')
585
585
586 return _commentlines(msg)
586 return _commentlines(msg)
587
587
588 def _helpmessage(continuecmd, abortcmd):
588 def _helpmessage(continuecmd, abortcmd):
589 msg = _('To continue: %s\n'
589 msg = _('To continue: %s\n'
590 'To abort: %s') % (continuecmd, abortcmd)
590 'To abort: %s') % (continuecmd, abortcmd)
591 return _commentlines(msg)
591 return _commentlines(msg)
592
592
593 def _rebasemsg():
593 def _rebasemsg():
594 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
594 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
595
595
596 def _histeditmsg():
596 def _histeditmsg():
597 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
597 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
598
598
599 def _unshelvemsg():
599 def _unshelvemsg():
600 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
600 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
601
601
602 def _updatecleanmsg(dest=None):
602 def _updatecleanmsg(dest=None):
603 warning = _('warning: this will discard uncommitted changes')
603 warning = _('warning: this will discard uncommitted changes')
604 return 'hg update --clean %s (%s)' % (dest or '.', warning)
604 return 'hg update --clean %s (%s)' % (dest or '.', warning)
605
605
606 def _graftmsg():
606 def _graftmsg():
607 # tweakdefaults requires `update` to have a rev hence the `.`
607 # tweakdefaults requires `update` to have a rev hence the `.`
608 return _helpmessage('hg graft --continue', _updatecleanmsg())
608 return _helpmessage('hg graft --continue', _updatecleanmsg())
609
609
610 def _mergemsg():
610 def _mergemsg():
611 # tweakdefaults requires `update` to have a rev hence the `.`
611 # tweakdefaults requires `update` to have a rev hence the `.`
612 return _helpmessage('hg commit', _updatecleanmsg())
612 return _helpmessage('hg commit', _updatecleanmsg())
613
613
614 def _bisectmsg():
614 def _bisectmsg():
615 msg = _('To mark the changeset good: hg bisect --good\n'
615 msg = _('To mark the changeset good: hg bisect --good\n'
616 'To mark the changeset bad: hg bisect --bad\n'
616 'To mark the changeset bad: hg bisect --bad\n'
617 'To abort: hg bisect --reset\n')
617 'To abort: hg bisect --reset\n')
618 return _commentlines(msg)
618 return _commentlines(msg)
619
619
620 def fileexistspredicate(filename):
620 def fileexistspredicate(filename):
621 return lambda repo: repo.vfs.exists(filename)
621 return lambda repo: repo.vfs.exists(filename)
622
622
623 def _mergepredicate(repo):
623 def _mergepredicate(repo):
624 return len(repo[None].parents()) > 1
624 return len(repo[None].parents()) > 1
625
625
626 STATES = (
626 STATES = (
627 # (state, predicate to detect states, helpful message function)
627 # (state, predicate to detect states, helpful message function)
628 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
628 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
629 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
629 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
630 ('graft', fileexistspredicate('graftstate'), _graftmsg),
630 ('graft', fileexistspredicate('graftstate'), _graftmsg),
631 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
631 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
632 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
632 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
633 # The merge state is part of a list that will be iterated over.
633 # The merge state is part of a list that will be iterated over.
634 # They need to be last because some of the other unfinished states may also
634 # They need to be last because some of the other unfinished states may also
635 # be in a merge or update state (eg. rebase, histedit, graft, etc).
635 # be in a merge or update state (eg. rebase, histedit, graft, etc).
636 # We want those to have priority.
636 # We want those to have priority.
637 ('merge', _mergepredicate, _mergemsg),
637 ('merge', _mergepredicate, _mergemsg),
638 )
638 )
639
639
640 def _getrepostate(repo):
640 def _getrepostate(repo):
641 # experimental config: commands.status.skipstates
641 # experimental config: commands.status.skipstates
642 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
642 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
643 for state, statedetectionpredicate, msgfn in STATES:
643 for state, statedetectionpredicate, msgfn in STATES:
644 if state in skip:
644 if state in skip:
645 continue
645 continue
646 if statedetectionpredicate(repo):
646 if statedetectionpredicate(repo):
647 return (state, statedetectionpredicate, msgfn)
647 return (state, statedetectionpredicate, msgfn)
648
648
649 def morestatus(repo, fm):
649 def morestatus(repo, fm):
650 statetuple = _getrepostate(repo)
650 statetuple = _getrepostate(repo)
651 label = 'status.morestatus'
651 label = 'status.morestatus'
652 if statetuple:
652 if statetuple:
653 fm.startitem()
653 fm.startitem()
654 state, statedetectionpredicate, helpfulmsg = statetuple
654 state, statedetectionpredicate, helpfulmsg = statetuple
655 statemsg = _('The repository is in an unfinished *%s* state.') % state
655 statemsg = _('The repository is in an unfinished *%s* state.') % state
656 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
656 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
657 conmsg = _conflictsmsg(repo)
657 conmsg = _conflictsmsg(repo)
658 if conmsg:
658 if conmsg:
659 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
659 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
660 if helpfulmsg:
660 if helpfulmsg:
661 helpmsg = helpfulmsg()
661 helpmsg = helpfulmsg()
662 fm.write('helpmsg', '%s\n', helpmsg, label=label)
662 fm.write('helpmsg', '%s\n', helpmsg, label=label)
663
663
664 def findpossible(cmd, table, strict=False):
664 def findpossible(cmd, table, strict=False):
665 """
665 """
666 Return cmd -> (aliases, command table entry)
666 Return cmd -> (aliases, command table entry)
667 for each matching command.
667 for each matching command.
668 Return debug commands (or their aliases) only if no normal command matches.
668 Return debug commands (or their aliases) only if no normal command matches.
669 """
669 """
670 choice = {}
670 choice = {}
671 debugchoice = {}
671 debugchoice = {}
672
672
673 if cmd in table:
673 if cmd in table:
674 # short-circuit exact matches, "log" alias beats "^log|history"
674 # short-circuit exact matches, "log" alias beats "^log|history"
675 keys = [cmd]
675 keys = [cmd]
676 else:
676 else:
677 keys = table.keys()
677 keys = table.keys()
678
678
679 allcmds = []
679 allcmds = []
680 for e in keys:
680 for e in keys:
681 aliases = parsealiases(e)
681 aliases = parsealiases(e)
682 allcmds.extend(aliases)
682 allcmds.extend(aliases)
683 found = None
683 found = None
684 if cmd in aliases:
684 if cmd in aliases:
685 found = cmd
685 found = cmd
686 elif not strict:
686 elif not strict:
687 for a in aliases:
687 for a in aliases:
688 if a.startswith(cmd):
688 if a.startswith(cmd):
689 found = a
689 found = a
690 break
690 break
691 if found is not None:
691 if found is not None:
692 if aliases[0].startswith("debug") or found.startswith("debug"):
692 if aliases[0].startswith("debug") or found.startswith("debug"):
693 debugchoice[found] = (aliases, table[e])
693 debugchoice[found] = (aliases, table[e])
694 else:
694 else:
695 choice[found] = (aliases, table[e])
695 choice[found] = (aliases, table[e])
696
696
697 if not choice and debugchoice:
697 if not choice and debugchoice:
698 choice = debugchoice
698 choice = debugchoice
699
699
700 return choice, allcmds
700 return choice, allcmds
701
701
702 def findcmd(cmd, table, strict=True):
702 def findcmd(cmd, table, strict=True):
703 """Return (aliases, command table entry) for command string."""
703 """Return (aliases, command table entry) for command string."""
704 choice, allcmds = findpossible(cmd, table, strict)
704 choice, allcmds = findpossible(cmd, table, strict)
705
705
706 if cmd in choice:
706 if cmd in choice:
707 return choice[cmd]
707 return choice[cmd]
708
708
709 if len(choice) > 1:
709 if len(choice) > 1:
710 clist = sorted(choice)
710 clist = sorted(choice)
711 raise error.AmbiguousCommand(cmd, clist)
711 raise error.AmbiguousCommand(cmd, clist)
712
712
713 if choice:
713 if choice:
714 return list(choice.values())[0]
714 return list(choice.values())[0]
715
715
716 raise error.UnknownCommand(cmd, allcmds)
716 raise error.UnknownCommand(cmd, allcmds)
717
717
718 def changebranch(ui, repo, revs, label):
718 def changebranch(ui, repo, revs, label):
719 """ Change the branch name of given revs to label """
719 """ Change the branch name of given revs to label """
720
720
721 with repo.wlock(), repo.lock(), repo.transaction('branches'):
721 with repo.wlock(), repo.lock(), repo.transaction('branches'):
722 # abort in case of uncommitted merge or dirty wdir
722 # abort in case of uncommitted merge or dirty wdir
723 bailifchanged(repo)
723 bailifchanged(repo)
724 revs = scmutil.revrange(repo, revs)
724 revs = scmutil.revrange(repo, revs)
725 if not revs:
725 if not revs:
726 raise error.Abort("empty revision set")
726 raise error.Abort("empty revision set")
727 roots = repo.revs('roots(%ld)', revs)
727 roots = repo.revs('roots(%ld)', revs)
728 if len(roots) > 1:
728 if len(roots) > 1:
729 raise error.Abort(_("cannot change branch of non-linear revisions"))
729 raise error.Abort(_("cannot change branch of non-linear revisions"))
730 rewriteutil.precheck(repo, revs, 'change branch of')
730 rewriteutil.precheck(repo, revs, 'change branch of')
731
731
732 root = repo[roots.first()]
732 root = repo[roots.first()]
733 if not root.p1().branch() == label and label in repo.branchmap():
733 if not root.p1().branch() == label and label in repo.branchmap():
734 raise error.Abort(_("a branch of the same name already exists"))
734 raise error.Abort(_("a branch of the same name already exists"))
735
735
736 if repo.revs('merge() and %ld', revs):
736 if repo.revs('merge() and %ld', revs):
737 raise error.Abort(_("cannot change branch of a merge commit"))
737 raise error.Abort(_("cannot change branch of a merge commit"))
738 if repo.revs('obsolete() and %ld', revs):
738 if repo.revs('obsolete() and %ld', revs):
739 raise error.Abort(_("cannot change branch of a obsolete changeset"))
739 raise error.Abort(_("cannot change branch of a obsolete changeset"))
740
740
741 # make sure only topological heads
741 # make sure only topological heads
742 if repo.revs('heads(%ld) - head()', revs):
742 if repo.revs('heads(%ld) - head()', revs):
743 raise error.Abort(_("cannot change branch in middle of a stack"))
743 raise error.Abort(_("cannot change branch in middle of a stack"))
744
744
745 replacements = {}
745 replacements = {}
746 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
746 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
747 # mercurial.subrepo -> mercurial.cmdutil
747 # mercurial.subrepo -> mercurial.cmdutil
748 from . import context
748 from . import context
749 for rev in revs:
749 for rev in revs:
750 ctx = repo[rev]
750 ctx = repo[rev]
751 oldbranch = ctx.branch()
751 oldbranch = ctx.branch()
752 # check if ctx has same branch
752 # check if ctx has same branch
753 if oldbranch == label:
753 if oldbranch == label:
754 continue
754 continue
755
755
756 def filectxfn(repo, newctx, path):
756 def filectxfn(repo, newctx, path):
757 try:
757 try:
758 return ctx[path]
758 return ctx[path]
759 except error.ManifestLookupError:
759 except error.ManifestLookupError:
760 return None
760 return None
761
761
762 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
762 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
763 % (hex(ctx.node()), oldbranch, label))
763 % (hex(ctx.node()), oldbranch, label))
764 extra = ctx.extra()
764 extra = ctx.extra()
765 extra['branch_change'] = hex(ctx.node())
765 extra['branch_change'] = hex(ctx.node())
766 # While changing branch of set of linear commits, make sure that
766 # While changing branch of set of linear commits, make sure that
767 # we base our commits on new parent rather than old parent which
767 # we base our commits on new parent rather than old parent which
768 # was obsoleted while changing the branch
768 # was obsoleted while changing the branch
769 p1 = ctx.p1().node()
769 p1 = ctx.p1().node()
770 p2 = ctx.p2().node()
770 p2 = ctx.p2().node()
771 if p1 in replacements:
771 if p1 in replacements:
772 p1 = replacements[p1][0]
772 p1 = replacements[p1][0]
773 if p2 in replacements:
773 if p2 in replacements:
774 p2 = replacements[p2][0]
774 p2 = replacements[p2][0]
775
775
776 mc = context.memctx(repo, (p1, p2),
776 mc = context.memctx(repo, (p1, p2),
777 ctx.description(),
777 ctx.description(),
778 ctx.files(),
778 ctx.files(),
779 filectxfn,
779 filectxfn,
780 user=ctx.user(),
780 user=ctx.user(),
781 date=ctx.date(),
781 date=ctx.date(),
782 extra=extra,
782 extra=extra,
783 branch=label)
783 branch=label)
784
784
785 commitphase = ctx.phase()
785 commitphase = ctx.phase()
786 overrides = {('phases', 'new-commit'): commitphase}
786 overrides = {('phases', 'new-commit'): commitphase}
787 with repo.ui.configoverride(overrides, 'branch-change'):
787 with repo.ui.configoverride(overrides, 'branch-change'):
788 newnode = repo.commitctx(mc)
788 newnode = repo.commitctx(mc)
789
789
790 replacements[ctx.node()] = (newnode,)
790 replacements[ctx.node()] = (newnode,)
791 ui.debug('new node id is %s\n' % hex(newnode))
791 ui.debug('new node id is %s\n' % hex(newnode))
792
792
793 # create obsmarkers and move bookmarks
793 # create obsmarkers and move bookmarks
794 scmutil.cleanupnodes(repo, replacements, 'branch-change')
794 scmutil.cleanupnodes(repo, replacements, 'branch-change')
795
795
796 # move the working copy too
796 # move the working copy too
797 wctx = repo[None]
797 wctx = repo[None]
798 # in-progress merge is a bit too complex for now.
798 # in-progress merge is a bit too complex for now.
799 if len(wctx.parents()) == 1:
799 if len(wctx.parents()) == 1:
800 newid = replacements.get(wctx.p1().node())
800 newid = replacements.get(wctx.p1().node())
801 if newid is not None:
801 if newid is not None:
802 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
802 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
803 # mercurial.cmdutil
803 # mercurial.cmdutil
804 from . import hg
804 from . import hg
805 hg.update(repo, newid[0], quietempty=True)
805 hg.update(repo, newid[0], quietempty=True)
806
806
807 ui.status(_("changed branch on %d changesets\n") % len(replacements))
807 ui.status(_("changed branch on %d changesets\n") % len(replacements))
808
808
809 def findrepo(p):
809 def findrepo(p):
810 while not os.path.isdir(os.path.join(p, ".hg")):
810 while not os.path.isdir(os.path.join(p, ".hg")):
811 oldp, p = p, os.path.dirname(p)
811 oldp, p = p, os.path.dirname(p)
812 if p == oldp:
812 if p == oldp:
813 return None
813 return None
814
814
815 return p
815 return p
816
816
817 def bailifchanged(repo, merge=True, hint=None):
817 def bailifchanged(repo, merge=True, hint=None):
818 """ enforce the precondition that working directory must be clean.
818 """ enforce the precondition that working directory must be clean.
819
819
820 'merge' can be set to false if a pending uncommitted merge should be
820 'merge' can be set to false if a pending uncommitted merge should be
821 ignored (such as when 'update --check' runs).
821 ignored (such as when 'update --check' runs).
822
822
823 'hint' is the usual hint given to Abort exception.
823 'hint' is the usual hint given to Abort exception.
824 """
824 """
825
825
826 if merge and repo.dirstate.p2() != nullid:
826 if merge and repo.dirstate.p2() != nullid:
827 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
827 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
828 modified, added, removed, deleted = repo.status()[:4]
828 modified, added, removed, deleted = repo.status()[:4]
829 if modified or added or removed or deleted:
829 if modified or added or removed or deleted:
830 raise error.Abort(_('uncommitted changes'), hint=hint)
830 raise error.Abort(_('uncommitted changes'), hint=hint)
831 ctx = repo[None]
831 ctx = repo[None]
832 for s in sorted(ctx.substate):
832 for s in sorted(ctx.substate):
833 ctx.sub(s).bailifchanged(hint=hint)
833 ctx.sub(s).bailifchanged(hint=hint)
834
834
835 def logmessage(ui, opts):
835 def logmessage(ui, opts):
836 """ get the log message according to -m and -l option """
836 """ get the log message according to -m and -l option """
837 message = opts.get('message')
837 message = opts.get('message')
838 logfile = opts.get('logfile')
838 logfile = opts.get('logfile')
839
839
840 if message and logfile:
840 if message and logfile:
841 raise error.Abort(_('options --message and --logfile are mutually '
841 raise error.Abort(_('options --message and --logfile are mutually '
842 'exclusive'))
842 'exclusive'))
843 if not message and logfile:
843 if not message and logfile:
844 try:
844 try:
845 if isstdiofilename(logfile):
845 if isstdiofilename(logfile):
846 message = ui.fin.read()
846 message = ui.fin.read()
847 else:
847 else:
848 message = '\n'.join(util.readfile(logfile).splitlines())
848 message = '\n'.join(util.readfile(logfile).splitlines())
849 except IOError as inst:
849 except IOError as inst:
850 raise error.Abort(_("can't read commit message '%s': %s") %
850 raise error.Abort(_("can't read commit message '%s': %s") %
851 (logfile, encoding.strtolocal(inst.strerror)))
851 (logfile, encoding.strtolocal(inst.strerror)))
852 return message
852 return message
853
853
854 def mergeeditform(ctxorbool, baseformname):
854 def mergeeditform(ctxorbool, baseformname):
855 """return appropriate editform name (referencing a committemplate)
855 """return appropriate editform name (referencing a committemplate)
856
856
857 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
857 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
858 merging is committed.
858 merging is committed.
859
859
860 This returns baseformname with '.merge' appended if it is a merge,
860 This returns baseformname with '.merge' appended if it is a merge,
861 otherwise '.normal' is appended.
861 otherwise '.normal' is appended.
862 """
862 """
863 if isinstance(ctxorbool, bool):
863 if isinstance(ctxorbool, bool):
864 if ctxorbool:
864 if ctxorbool:
865 return baseformname + ".merge"
865 return baseformname + ".merge"
866 elif 1 < len(ctxorbool.parents()):
866 elif 1 < len(ctxorbool.parents()):
867 return baseformname + ".merge"
867 return baseformname + ".merge"
868
868
869 return baseformname + ".normal"
869 return baseformname + ".normal"
870
870
871 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
871 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
872 editform='', **opts):
872 editform='', **opts):
873 """get appropriate commit message editor according to '--edit' option
873 """get appropriate commit message editor according to '--edit' option
874
874
875 'finishdesc' is a function to be called with edited commit message
875 'finishdesc' is a function to be called with edited commit message
876 (= 'description' of the new changeset) just after editing, but
876 (= 'description' of the new changeset) just after editing, but
877 before checking empty-ness. It should return actual text to be
877 before checking empty-ness. It should return actual text to be
878 stored into history. This allows to change description before
878 stored into history. This allows to change description before
879 storing.
879 storing.
880
880
881 'extramsg' is a extra message to be shown in the editor instead of
881 'extramsg' is a extra message to be shown in the editor instead of
882 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
882 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
883 is automatically added.
883 is automatically added.
884
884
885 'editform' is a dot-separated list of names, to distinguish
885 'editform' is a dot-separated list of names, to distinguish
886 the purpose of commit text editing.
886 the purpose of commit text editing.
887
887
888 'getcommiteditor' returns 'commitforceeditor' regardless of
888 'getcommiteditor' returns 'commitforceeditor' regardless of
889 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
889 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
890 they are specific for usage in MQ.
890 they are specific for usage in MQ.
891 """
891 """
892 if edit or finishdesc or extramsg:
892 if edit or finishdesc or extramsg:
893 return lambda r, c, s: commitforceeditor(r, c, s,
893 return lambda r, c, s: commitforceeditor(r, c, s,
894 finishdesc=finishdesc,
894 finishdesc=finishdesc,
895 extramsg=extramsg,
895 extramsg=extramsg,
896 editform=editform)
896 editform=editform)
897 elif editform:
897 elif editform:
898 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
898 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
899 else:
899 else:
900 return commiteditor
900 return commiteditor
901
901
902 def rendertemplate(ctx, tmpl, props=None):
902 def rendertemplate(ctx, tmpl, props=None):
903 """Expand a literal template 'tmpl' byte-string against one changeset
903 """Expand a literal template 'tmpl' byte-string against one changeset
904
904
905 Each props item must be a stringify-able value or a callable returning
905 Each props item must be a stringify-able value or a callable returning
906 such value, i.e. no bare list nor dict should be passed.
906 such value, i.e. no bare list nor dict should be passed.
907 """
907 """
908 repo = ctx.repo()
908 repo = ctx.repo()
909 tres = formatter.templateresources(repo.ui, repo)
909 tres = formatter.templateresources(repo.ui, repo)
910 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
910 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
911 resources=tres)
911 resources=tres)
912 mapping = {'ctx': ctx, 'revcache': {}}
912 mapping = {'ctx': ctx, 'revcache': {}}
913 if props:
913 if props:
914 mapping.update(props)
914 mapping.update(props)
915 return t.renderdefault(mapping)
915 return t.renderdefault(mapping)
916
916
917 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
917 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
918 r"""Convert old-style filename format string to template string
918 r"""Convert old-style filename format string to template string
919
919
920 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
920 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
921 'foo-{reporoot|basename}-{seqno}.patch'
921 'foo-{reporoot|basename}-{seqno}.patch'
922 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
922 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
923 '{rev}{tags % "{tag}"}{node}'
923 '{rev}{tags % "{tag}"}{node}'
924
924
925 '\' in outermost strings has to be escaped because it is a directory
925 '\' in outermost strings has to be escaped because it is a directory
926 separator on Windows:
926 separator on Windows:
927
927
928 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
928 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
929 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
929 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
930 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
930 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
931 '\\\\\\\\foo\\\\bar.patch'
931 '\\\\\\\\foo\\\\bar.patch'
932 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
932 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
933 '\\\\{tags % "{tag}"}'
933 '\\\\{tags % "{tag}"}'
934
934
935 but inner strings follow the template rules (i.e. '\' is taken as an
935 but inner strings follow the template rules (i.e. '\' is taken as an
936 escape character):
936 escape character):
937
937
938 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
938 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
939 '{"c:\\tmp"}'
939 '{"c:\\tmp"}'
940 """
940 """
941 expander = {
941 expander = {
942 b'H': b'{node}',
942 b'H': b'{node}',
943 b'R': b'{rev}',
943 b'R': b'{rev}',
944 b'h': b'{node|short}',
944 b'h': b'{node|short}',
945 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
945 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
946 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
946 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
947 b'%': b'%',
947 b'%': b'%',
948 b'b': b'{reporoot|basename}',
948 b'b': b'{reporoot|basename}',
949 }
949 }
950 if total is not None:
950 if total is not None:
951 expander[b'N'] = b'{total}'
951 expander[b'N'] = b'{total}'
952 if seqno is not None:
952 if seqno is not None:
953 expander[b'n'] = b'{seqno}'
953 expander[b'n'] = b'{seqno}'
954 if total is not None and seqno is not None:
954 if total is not None and seqno is not None:
955 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
955 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
956 if pathname is not None:
956 if pathname is not None:
957 expander[b's'] = b'{pathname|basename}'
957 expander[b's'] = b'{pathname|basename}'
958 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
958 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
959 expander[b'p'] = b'{pathname}'
959 expander[b'p'] = b'{pathname}'
960
960
961 newname = []
961 newname = []
962 for typ, start, end in templater.scantemplate(pat, raw=True):
962 for typ, start, end in templater.scantemplate(pat, raw=True):
963 if typ != b'string':
963 if typ != b'string':
964 newname.append(pat[start:end])
964 newname.append(pat[start:end])
965 continue
965 continue
966 i = start
966 i = start
967 while i < end:
967 while i < end:
968 n = pat.find(b'%', i, end)
968 n = pat.find(b'%', i, end)
969 if n < 0:
969 if n < 0:
970 newname.append(stringutil.escapestr(pat[i:end]))
970 newname.append(stringutil.escapestr(pat[i:end]))
971 break
971 break
972 newname.append(stringutil.escapestr(pat[i:n]))
972 newname.append(stringutil.escapestr(pat[i:n]))
973 if n + 2 > end:
973 if n + 2 > end:
974 raise error.Abort(_("incomplete format spec in output "
974 raise error.Abort(_("incomplete format spec in output "
975 "filename"))
975 "filename"))
976 c = pat[n + 1:n + 2]
976 c = pat[n + 1:n + 2]
977 i = n + 2
977 i = n + 2
978 try:
978 try:
979 newname.append(expander[c])
979 newname.append(expander[c])
980 except KeyError:
980 except KeyError:
981 raise error.Abort(_("invalid format spec '%%%s' in output "
981 raise error.Abort(_("invalid format spec '%%%s' in output "
982 "filename") % c)
982 "filename") % c)
983 return ''.join(newname)
983 return ''.join(newname)
984
984
985 def makefilename(ctx, pat, **props):
985 def makefilename(ctx, pat, **props):
986 if not pat:
986 if not pat:
987 return pat
987 return pat
988 tmpl = _buildfntemplate(pat, **props)
988 tmpl = _buildfntemplate(pat, **props)
989 # BUG: alias expansion shouldn't be made against template fragments
989 # BUG: alias expansion shouldn't be made against template fragments
990 # rewritten from %-format strings, but we have no easy way to partially
990 # rewritten from %-format strings, but we have no easy way to partially
991 # disable the expansion.
991 # disable the expansion.
992 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
992 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
993
993
994 def isstdiofilename(pat):
994 def isstdiofilename(pat):
995 """True if the given pat looks like a filename denoting stdin/stdout"""
995 """True if the given pat looks like a filename denoting stdin/stdout"""
996 return not pat or pat == '-'
996 return not pat or pat == '-'
997
997
998 class _unclosablefile(object):
998 class _unclosablefile(object):
999 def __init__(self, fp):
999 def __init__(self, fp):
1000 self._fp = fp
1000 self._fp = fp
1001
1001
1002 def close(self):
1002 def close(self):
1003 pass
1003 pass
1004
1004
1005 def __iter__(self):
1005 def __iter__(self):
1006 return iter(self._fp)
1006 return iter(self._fp)
1007
1007
1008 def __getattr__(self, attr):
1008 def __getattr__(self, attr):
1009 return getattr(self._fp, attr)
1009 return getattr(self._fp, attr)
1010
1010
1011 def __enter__(self):
1011 def __enter__(self):
1012 return self
1012 return self
1013
1013
1014 def __exit__(self, exc_type, exc_value, exc_tb):
1014 def __exit__(self, exc_type, exc_value, exc_tb):
1015 pass
1015 pass
1016
1016
1017 def makefileobj(ctx, pat, mode='wb', modemap=None, **props):
1017 def makefileobj(ctx, pat, mode='wb', modemap=None, **props):
1018 writable = mode not in ('r', 'rb')
1018 writable = mode not in ('r', 'rb')
1019
1019
1020 if isstdiofilename(pat):
1020 if isstdiofilename(pat):
1021 repo = ctx.repo()
1021 repo = ctx.repo()
1022 if writable:
1022 if writable:
1023 fp = repo.ui.fout
1023 fp = repo.ui.fout
1024 else:
1024 else:
1025 fp = repo.ui.fin
1025 fp = repo.ui.fin
1026 return _unclosablefile(fp)
1026 return _unclosablefile(fp)
1027 fn = makefilename(ctx, pat, **props)
1027 fn = makefilename(ctx, pat, **props)
1028 if modemap is not None:
1028 if modemap is not None:
1029 mode = modemap.get(fn, mode)
1029 mode = modemap.get(fn, mode)
1030 if mode == 'wb':
1030 if mode == 'wb':
1031 modemap[fn] = 'ab'
1031 modemap[fn] = 'ab'
1032 return open(fn, mode)
1032 return open(fn, mode)
1033
1033
1034 def openrevlog(repo, cmd, file_, opts):
1034 def openrevlog(repo, cmd, file_, opts):
1035 """opens the changelog, manifest, a filelog or a given revlog"""
1035 """opens the changelog, manifest, a filelog or a given revlog"""
1036 cl = opts['changelog']
1036 cl = opts['changelog']
1037 mf = opts['manifest']
1037 mf = opts['manifest']
1038 dir = opts['dir']
1038 dir = opts['dir']
1039 msg = None
1039 msg = None
1040 if cl and mf:
1040 if cl and mf:
1041 msg = _('cannot specify --changelog and --manifest at the same time')
1041 msg = _('cannot specify --changelog and --manifest at the same time')
1042 elif cl and dir:
1042 elif cl and dir:
1043 msg = _('cannot specify --changelog and --dir at the same time')
1043 msg = _('cannot specify --changelog and --dir at the same time')
1044 elif cl or mf or dir:
1044 elif cl or mf or dir:
1045 if file_:
1045 if file_:
1046 msg = _('cannot specify filename with --changelog or --manifest')
1046 msg = _('cannot specify filename with --changelog or --manifest')
1047 elif not repo:
1047 elif not repo:
1048 msg = _('cannot specify --changelog or --manifest or --dir '
1048 msg = _('cannot specify --changelog or --manifest or --dir '
1049 'without a repository')
1049 'without a repository')
1050 if msg:
1050 if msg:
1051 raise error.Abort(msg)
1051 raise error.Abort(msg)
1052
1052
1053 r = None
1053 r = None
1054 if repo:
1054 if repo:
1055 if cl:
1055 if cl:
1056 r = repo.unfiltered().changelog
1056 r = repo.unfiltered().changelog
1057 elif dir:
1057 elif dir:
1058 if 'treemanifest' not in repo.requirements:
1058 if 'treemanifest' not in repo.requirements:
1059 raise error.Abort(_("--dir can only be used on repos with "
1059 raise error.Abort(_("--dir can only be used on repos with "
1060 "treemanifest enabled"))
1060 "treemanifest enabled"))
1061 dirlog = repo.manifestlog._revlog.dirlog(dir)
1061 dirlog = repo.manifestlog._revlog.dirlog(dir)
1062 if len(dirlog):
1062 if len(dirlog):
1063 r = dirlog
1063 r = dirlog
1064 elif mf:
1064 elif mf:
1065 r = repo.manifestlog._revlog
1065 r = repo.manifestlog._revlog
1066 elif file_:
1066 elif file_:
1067 filelog = repo.file(file_)
1067 filelog = repo.file(file_)
1068 if len(filelog):
1068 if len(filelog):
1069 r = filelog
1069 r = filelog
1070 if not r:
1070 if not r:
1071 if not file_:
1071 if not file_:
1072 raise error.CommandError(cmd, _('invalid arguments'))
1072 raise error.CommandError(cmd, _('invalid arguments'))
1073 if not os.path.isfile(file_):
1073 if not os.path.isfile(file_):
1074 raise error.Abort(_("revlog '%s' not found") % file_)
1074 raise error.Abort(_("revlog '%s' not found") % file_)
1075 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1075 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1076 file_[:-2] + ".i")
1076 file_[:-2] + ".i")
1077 return r
1077 return r
1078
1078
1079 def copy(ui, repo, pats, opts, rename=False):
1079 def copy(ui, repo, pats, opts, rename=False):
1080 # called with the repo lock held
1080 # called with the repo lock held
1081 #
1081 #
1082 # hgsep => pathname that uses "/" to separate directories
1082 # hgsep => pathname that uses "/" to separate directories
1083 # ossep => pathname that uses os.sep to separate directories
1083 # ossep => pathname that uses os.sep to separate directories
1084 cwd = repo.getcwd()
1084 cwd = repo.getcwd()
1085 targets = {}
1085 targets = {}
1086 after = opts.get("after")
1086 after = opts.get("after")
1087 dryrun = opts.get("dry_run")
1087 dryrun = opts.get("dry_run")
1088 wctx = repo[None]
1088 wctx = repo[None]
1089
1089
1090 def walkpat(pat):
1090 def walkpat(pat):
1091 srcs = []
1091 srcs = []
1092 if after:
1092 if after:
1093 badstates = '?'
1093 badstates = '?'
1094 else:
1094 else:
1095 badstates = '?r'
1095 badstates = '?r'
1096 m = scmutil.match(wctx, [pat], opts, globbed=True)
1096 m = scmutil.match(wctx, [pat], opts, globbed=True)
1097 for abs in wctx.walk(m):
1097 for abs in wctx.walk(m):
1098 state = repo.dirstate[abs]
1098 state = repo.dirstate[abs]
1099 rel = m.rel(abs)
1099 rel = m.rel(abs)
1100 exact = m.exact(abs)
1100 exact = m.exact(abs)
1101 if state in badstates:
1101 if state in badstates:
1102 if exact and state == '?':
1102 if exact and state == '?':
1103 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1103 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1104 if exact and state == 'r':
1104 if exact and state == 'r':
1105 ui.warn(_('%s: not copying - file has been marked for'
1105 ui.warn(_('%s: not copying - file has been marked for'
1106 ' remove\n') % rel)
1106 ' remove\n') % rel)
1107 continue
1107 continue
1108 # abs: hgsep
1108 # abs: hgsep
1109 # rel: ossep
1109 # rel: ossep
1110 srcs.append((abs, rel, exact))
1110 srcs.append((abs, rel, exact))
1111 return srcs
1111 return srcs
1112
1112
1113 # abssrc: hgsep
1113 # abssrc: hgsep
1114 # relsrc: ossep
1114 # relsrc: ossep
1115 # otarget: ossep
1115 # otarget: ossep
1116 def copyfile(abssrc, relsrc, otarget, exact):
1116 def copyfile(abssrc, relsrc, otarget, exact):
1117 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1117 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1118 if '/' in abstarget:
1118 if '/' in abstarget:
1119 # We cannot normalize abstarget itself, this would prevent
1119 # We cannot normalize abstarget itself, this would prevent
1120 # case only renames, like a => A.
1120 # case only renames, like a => A.
1121 abspath, absname = abstarget.rsplit('/', 1)
1121 abspath, absname = abstarget.rsplit('/', 1)
1122 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1122 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1123 reltarget = repo.pathto(abstarget, cwd)
1123 reltarget = repo.pathto(abstarget, cwd)
1124 target = repo.wjoin(abstarget)
1124 target = repo.wjoin(abstarget)
1125 src = repo.wjoin(abssrc)
1125 src = repo.wjoin(abssrc)
1126 state = repo.dirstate[abstarget]
1126 state = repo.dirstate[abstarget]
1127
1127
1128 scmutil.checkportable(ui, abstarget)
1128 scmutil.checkportable(ui, abstarget)
1129
1129
1130 # check for collisions
1130 # check for collisions
1131 prevsrc = targets.get(abstarget)
1131 prevsrc = targets.get(abstarget)
1132 if prevsrc is not None:
1132 if prevsrc is not None:
1133 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1133 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1134 (reltarget, repo.pathto(abssrc, cwd),
1134 (reltarget, repo.pathto(abssrc, cwd),
1135 repo.pathto(prevsrc, cwd)))
1135 repo.pathto(prevsrc, cwd)))
1136 return
1136 return
1137
1137
1138 # check for overwrites
1138 # check for overwrites
1139 exists = os.path.lexists(target)
1139 exists = os.path.lexists(target)
1140 samefile = False
1140 samefile = False
1141 if exists and abssrc != abstarget:
1141 if exists and abssrc != abstarget:
1142 if (repo.dirstate.normalize(abssrc) ==
1142 if (repo.dirstate.normalize(abssrc) ==
1143 repo.dirstate.normalize(abstarget)):
1143 repo.dirstate.normalize(abstarget)):
1144 if not rename:
1144 if not rename:
1145 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1145 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1146 return
1146 return
1147 exists = False
1147 exists = False
1148 samefile = True
1148 samefile = True
1149
1149
1150 if not after and exists or after and state in 'mn':
1150 if not after and exists or after and state in 'mn':
1151 if not opts['force']:
1151 if not opts['force']:
1152 if state in 'mn':
1152 if state in 'mn':
1153 msg = _('%s: not overwriting - file already committed\n')
1153 msg = _('%s: not overwriting - file already committed\n')
1154 if after:
1154 if after:
1155 flags = '--after --force'
1155 flags = '--after --force'
1156 else:
1156 else:
1157 flags = '--force'
1157 flags = '--force'
1158 if rename:
1158 if rename:
1159 hint = _('(hg rename %s to replace the file by '
1159 hint = _('(hg rename %s to replace the file by '
1160 'recording a rename)\n') % flags
1160 'recording a rename)\n') % flags
1161 else:
1161 else:
1162 hint = _('(hg copy %s to replace the file by '
1162 hint = _('(hg copy %s to replace the file by '
1163 'recording a copy)\n') % flags
1163 'recording a copy)\n') % flags
1164 else:
1164 else:
1165 msg = _('%s: not overwriting - file exists\n')
1165 msg = _('%s: not overwriting - file exists\n')
1166 if rename:
1166 if rename:
1167 hint = _('(hg rename --after to record the rename)\n')
1167 hint = _('(hg rename --after to record the rename)\n')
1168 else:
1168 else:
1169 hint = _('(hg copy --after to record the copy)\n')
1169 hint = _('(hg copy --after to record the copy)\n')
1170 ui.warn(msg % reltarget)
1170 ui.warn(msg % reltarget)
1171 ui.warn(hint)
1171 ui.warn(hint)
1172 return
1172 return
1173
1173
1174 if after:
1174 if after:
1175 if not exists:
1175 if not exists:
1176 if rename:
1176 if rename:
1177 ui.warn(_('%s: not recording move - %s does not exist\n') %
1177 ui.warn(_('%s: not recording move - %s does not exist\n') %
1178 (relsrc, reltarget))
1178 (relsrc, reltarget))
1179 else:
1179 else:
1180 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1180 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1181 (relsrc, reltarget))
1181 (relsrc, reltarget))
1182 return
1182 return
1183 elif not dryrun:
1183 elif not dryrun:
1184 try:
1184 try:
1185 if exists:
1185 if exists:
1186 os.unlink(target)
1186 os.unlink(target)
1187 targetdir = os.path.dirname(target) or '.'
1187 targetdir = os.path.dirname(target) or '.'
1188 if not os.path.isdir(targetdir):
1188 if not os.path.isdir(targetdir):
1189 os.makedirs(targetdir)
1189 os.makedirs(targetdir)
1190 if samefile:
1190 if samefile:
1191 tmp = target + "~hgrename"
1191 tmp = target + "~hgrename"
1192 os.rename(src, tmp)
1192 os.rename(src, tmp)
1193 os.rename(tmp, target)
1193 os.rename(tmp, target)
1194 else:
1194 else:
1195 util.copyfile(src, target)
1195 # Preserve stat info on renames, not on copies; this matches
1196 # Linux CLI behavior.
1197 util.copyfile(src, target, copystat=rename)
1196 srcexists = True
1198 srcexists = True
1197 except IOError as inst:
1199 except IOError as inst:
1198 if inst.errno == errno.ENOENT:
1200 if inst.errno == errno.ENOENT:
1199 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1201 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1200 srcexists = False
1202 srcexists = False
1201 else:
1203 else:
1202 ui.warn(_('%s: cannot copy - %s\n') %
1204 ui.warn(_('%s: cannot copy - %s\n') %
1203 (relsrc, encoding.strtolocal(inst.strerror)))
1205 (relsrc, encoding.strtolocal(inst.strerror)))
1204 return True # report a failure
1206 return True # report a failure
1205
1207
1206 if ui.verbose or not exact:
1208 if ui.verbose or not exact:
1207 if rename:
1209 if rename:
1208 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1210 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1209 else:
1211 else:
1210 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1212 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1211
1213
1212 targets[abstarget] = abssrc
1214 targets[abstarget] = abssrc
1213
1215
1214 # fix up dirstate
1216 # fix up dirstate
1215 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1217 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1216 dryrun=dryrun, cwd=cwd)
1218 dryrun=dryrun, cwd=cwd)
1217 if rename and not dryrun:
1219 if rename and not dryrun:
1218 if not after and srcexists and not samefile:
1220 if not after and srcexists and not samefile:
1219 repo.wvfs.unlinkpath(abssrc)
1221 repo.wvfs.unlinkpath(abssrc)
1220 wctx.forget([abssrc])
1222 wctx.forget([abssrc])
1221
1223
1222 # pat: ossep
1224 # pat: ossep
1223 # dest ossep
1225 # dest ossep
1224 # srcs: list of (hgsep, hgsep, ossep, bool)
1226 # srcs: list of (hgsep, hgsep, ossep, bool)
1225 # return: function that takes hgsep and returns ossep
1227 # return: function that takes hgsep and returns ossep
1226 def targetpathfn(pat, dest, srcs):
1228 def targetpathfn(pat, dest, srcs):
1227 if os.path.isdir(pat):
1229 if os.path.isdir(pat):
1228 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1230 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1229 abspfx = util.localpath(abspfx)
1231 abspfx = util.localpath(abspfx)
1230 if destdirexists:
1232 if destdirexists:
1231 striplen = len(os.path.split(abspfx)[0])
1233 striplen = len(os.path.split(abspfx)[0])
1232 else:
1234 else:
1233 striplen = len(abspfx)
1235 striplen = len(abspfx)
1234 if striplen:
1236 if striplen:
1235 striplen += len(pycompat.ossep)
1237 striplen += len(pycompat.ossep)
1236 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1238 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1237 elif destdirexists:
1239 elif destdirexists:
1238 res = lambda p: os.path.join(dest,
1240 res = lambda p: os.path.join(dest,
1239 os.path.basename(util.localpath(p)))
1241 os.path.basename(util.localpath(p)))
1240 else:
1242 else:
1241 res = lambda p: dest
1243 res = lambda p: dest
1242 return res
1244 return res
1243
1245
1244 # pat: ossep
1246 # pat: ossep
1245 # dest ossep
1247 # dest ossep
1246 # srcs: list of (hgsep, hgsep, ossep, bool)
1248 # srcs: list of (hgsep, hgsep, ossep, bool)
1247 # return: function that takes hgsep and returns ossep
1249 # return: function that takes hgsep and returns ossep
1248 def targetpathafterfn(pat, dest, srcs):
1250 def targetpathafterfn(pat, dest, srcs):
1249 if matchmod.patkind(pat):
1251 if matchmod.patkind(pat):
1250 # a mercurial pattern
1252 # a mercurial pattern
1251 res = lambda p: os.path.join(dest,
1253 res = lambda p: os.path.join(dest,
1252 os.path.basename(util.localpath(p)))
1254 os.path.basename(util.localpath(p)))
1253 else:
1255 else:
1254 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1256 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1255 if len(abspfx) < len(srcs[0][0]):
1257 if len(abspfx) < len(srcs[0][0]):
1256 # A directory. Either the target path contains the last
1258 # A directory. Either the target path contains the last
1257 # component of the source path or it does not.
1259 # component of the source path or it does not.
1258 def evalpath(striplen):
1260 def evalpath(striplen):
1259 score = 0
1261 score = 0
1260 for s in srcs:
1262 for s in srcs:
1261 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1263 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1262 if os.path.lexists(t):
1264 if os.path.lexists(t):
1263 score += 1
1265 score += 1
1264 return score
1266 return score
1265
1267
1266 abspfx = util.localpath(abspfx)
1268 abspfx = util.localpath(abspfx)
1267 striplen = len(abspfx)
1269 striplen = len(abspfx)
1268 if striplen:
1270 if striplen:
1269 striplen += len(pycompat.ossep)
1271 striplen += len(pycompat.ossep)
1270 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1272 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1271 score = evalpath(striplen)
1273 score = evalpath(striplen)
1272 striplen1 = len(os.path.split(abspfx)[0])
1274 striplen1 = len(os.path.split(abspfx)[0])
1273 if striplen1:
1275 if striplen1:
1274 striplen1 += len(pycompat.ossep)
1276 striplen1 += len(pycompat.ossep)
1275 if evalpath(striplen1) > score:
1277 if evalpath(striplen1) > score:
1276 striplen = striplen1
1278 striplen = striplen1
1277 res = lambda p: os.path.join(dest,
1279 res = lambda p: os.path.join(dest,
1278 util.localpath(p)[striplen:])
1280 util.localpath(p)[striplen:])
1279 else:
1281 else:
1280 # a file
1282 # a file
1281 if destdirexists:
1283 if destdirexists:
1282 res = lambda p: os.path.join(dest,
1284 res = lambda p: os.path.join(dest,
1283 os.path.basename(util.localpath(p)))
1285 os.path.basename(util.localpath(p)))
1284 else:
1286 else:
1285 res = lambda p: dest
1287 res = lambda p: dest
1286 return res
1288 return res
1287
1289
1288 pats = scmutil.expandpats(pats)
1290 pats = scmutil.expandpats(pats)
1289 if not pats:
1291 if not pats:
1290 raise error.Abort(_('no source or destination specified'))
1292 raise error.Abort(_('no source or destination specified'))
1291 if len(pats) == 1:
1293 if len(pats) == 1:
1292 raise error.Abort(_('no destination specified'))
1294 raise error.Abort(_('no destination specified'))
1293 dest = pats.pop()
1295 dest = pats.pop()
1294 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1296 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1295 if not destdirexists:
1297 if not destdirexists:
1296 if len(pats) > 1 or matchmod.patkind(pats[0]):
1298 if len(pats) > 1 or matchmod.patkind(pats[0]):
1297 raise error.Abort(_('with multiple sources, destination must be an '
1299 raise error.Abort(_('with multiple sources, destination must be an '
1298 'existing directory'))
1300 'existing directory'))
1299 if util.endswithsep(dest):
1301 if util.endswithsep(dest):
1300 raise error.Abort(_('destination %s is not a directory') % dest)
1302 raise error.Abort(_('destination %s is not a directory') % dest)
1301
1303
1302 tfn = targetpathfn
1304 tfn = targetpathfn
1303 if after:
1305 if after:
1304 tfn = targetpathafterfn
1306 tfn = targetpathafterfn
1305 copylist = []
1307 copylist = []
1306 for pat in pats:
1308 for pat in pats:
1307 srcs = walkpat(pat)
1309 srcs = walkpat(pat)
1308 if not srcs:
1310 if not srcs:
1309 continue
1311 continue
1310 copylist.append((tfn(pat, dest, srcs), srcs))
1312 copylist.append((tfn(pat, dest, srcs), srcs))
1311 if not copylist:
1313 if not copylist:
1312 raise error.Abort(_('no files to copy'))
1314 raise error.Abort(_('no files to copy'))
1313
1315
1314 errors = 0
1316 errors = 0
1315 for targetpath, srcs in copylist:
1317 for targetpath, srcs in copylist:
1316 for abssrc, relsrc, exact in srcs:
1318 for abssrc, relsrc, exact in srcs:
1317 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1319 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1318 errors += 1
1320 errors += 1
1319
1321
1320 if errors:
1322 if errors:
1321 ui.warn(_('(consider using --after)\n'))
1323 ui.warn(_('(consider using --after)\n'))
1322
1324
1323 return errors != 0
1325 return errors != 0
1324
1326
1325 ## facility to let extension process additional data into an import patch
1327 ## facility to let extension process additional data into an import patch
1326 # list of identifier to be executed in order
1328 # list of identifier to be executed in order
1327 extrapreimport = [] # run before commit
1329 extrapreimport = [] # run before commit
1328 extrapostimport = [] # run after commit
1330 extrapostimport = [] # run after commit
1329 # mapping from identifier to actual import function
1331 # mapping from identifier to actual import function
1330 #
1332 #
1331 # 'preimport' are run before the commit is made and are provided the following
1333 # 'preimport' are run before the commit is made and are provided the following
1332 # arguments:
1334 # arguments:
1333 # - repo: the localrepository instance,
1335 # - repo: the localrepository instance,
1334 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1336 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1335 # - extra: the future extra dictionary of the changeset, please mutate it,
1337 # - extra: the future extra dictionary of the changeset, please mutate it,
1336 # - opts: the import options.
1338 # - opts: the import options.
1337 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1339 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1338 # mutation of in memory commit and more. Feel free to rework the code to get
1340 # mutation of in memory commit and more. Feel free to rework the code to get
1339 # there.
1341 # there.
1340 extrapreimportmap = {}
1342 extrapreimportmap = {}
1341 # 'postimport' are run after the commit is made and are provided the following
1343 # 'postimport' are run after the commit is made and are provided the following
1342 # argument:
1344 # argument:
1343 # - ctx: the changectx created by import.
1345 # - ctx: the changectx created by import.
1344 extrapostimportmap = {}
1346 extrapostimportmap = {}
1345
1347
1346 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1348 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1347 """Utility function used by commands.import to import a single patch
1349 """Utility function used by commands.import to import a single patch
1348
1350
1349 This function is explicitly defined here to help the evolve extension to
1351 This function is explicitly defined here to help the evolve extension to
1350 wrap this part of the import logic.
1352 wrap this part of the import logic.
1351
1353
1352 The API is currently a bit ugly because it a simple code translation from
1354 The API is currently a bit ugly because it a simple code translation from
1353 the import command. Feel free to make it better.
1355 the import command. Feel free to make it better.
1354
1356
1355 :hunk: a patch (as a binary string)
1357 :hunk: a patch (as a binary string)
1356 :parents: nodes that will be parent of the created commit
1358 :parents: nodes that will be parent of the created commit
1357 :opts: the full dict of option passed to the import command
1359 :opts: the full dict of option passed to the import command
1358 :msgs: list to save commit message to.
1360 :msgs: list to save commit message to.
1359 (used in case we need to save it when failing)
1361 (used in case we need to save it when failing)
1360 :updatefunc: a function that update a repo to a given node
1362 :updatefunc: a function that update a repo to a given node
1361 updatefunc(<repo>, <node>)
1363 updatefunc(<repo>, <node>)
1362 """
1364 """
1363 # avoid cycle context -> subrepo -> cmdutil
1365 # avoid cycle context -> subrepo -> cmdutil
1364 from . import context
1366 from . import context
1365 extractdata = patch.extract(ui, hunk)
1367 extractdata = patch.extract(ui, hunk)
1366 tmpname = extractdata.get('filename')
1368 tmpname = extractdata.get('filename')
1367 message = extractdata.get('message')
1369 message = extractdata.get('message')
1368 user = opts.get('user') or extractdata.get('user')
1370 user = opts.get('user') or extractdata.get('user')
1369 date = opts.get('date') or extractdata.get('date')
1371 date = opts.get('date') or extractdata.get('date')
1370 branch = extractdata.get('branch')
1372 branch = extractdata.get('branch')
1371 nodeid = extractdata.get('nodeid')
1373 nodeid = extractdata.get('nodeid')
1372 p1 = extractdata.get('p1')
1374 p1 = extractdata.get('p1')
1373 p2 = extractdata.get('p2')
1375 p2 = extractdata.get('p2')
1374
1376
1375 nocommit = opts.get('no_commit')
1377 nocommit = opts.get('no_commit')
1376 importbranch = opts.get('import_branch')
1378 importbranch = opts.get('import_branch')
1377 update = not opts.get('bypass')
1379 update = not opts.get('bypass')
1378 strip = opts["strip"]
1380 strip = opts["strip"]
1379 prefix = opts["prefix"]
1381 prefix = opts["prefix"]
1380 sim = float(opts.get('similarity') or 0)
1382 sim = float(opts.get('similarity') or 0)
1381 if not tmpname:
1383 if not tmpname:
1382 return (None, None, False)
1384 return (None, None, False)
1383
1385
1384 rejects = False
1386 rejects = False
1385
1387
1386 try:
1388 try:
1387 cmdline_message = logmessage(ui, opts)
1389 cmdline_message = logmessage(ui, opts)
1388 if cmdline_message:
1390 if cmdline_message:
1389 # pickup the cmdline msg
1391 # pickup the cmdline msg
1390 message = cmdline_message
1392 message = cmdline_message
1391 elif message:
1393 elif message:
1392 # pickup the patch msg
1394 # pickup the patch msg
1393 message = message.strip()
1395 message = message.strip()
1394 else:
1396 else:
1395 # launch the editor
1397 # launch the editor
1396 message = None
1398 message = None
1397 ui.debug('message:\n%s\n' % message)
1399 ui.debug('message:\n%s\n' % message)
1398
1400
1399 if len(parents) == 1:
1401 if len(parents) == 1:
1400 parents.append(repo[nullid])
1402 parents.append(repo[nullid])
1401 if opts.get('exact'):
1403 if opts.get('exact'):
1402 if not nodeid or not p1:
1404 if not nodeid or not p1:
1403 raise error.Abort(_('not a Mercurial patch'))
1405 raise error.Abort(_('not a Mercurial patch'))
1404 p1 = repo[p1]
1406 p1 = repo[p1]
1405 p2 = repo[p2 or nullid]
1407 p2 = repo[p2 or nullid]
1406 elif p2:
1408 elif p2:
1407 try:
1409 try:
1408 p1 = repo[p1]
1410 p1 = repo[p1]
1409 p2 = repo[p2]
1411 p2 = repo[p2]
1410 # Without any options, consider p2 only if the
1412 # Without any options, consider p2 only if the
1411 # patch is being applied on top of the recorded
1413 # patch is being applied on top of the recorded
1412 # first parent.
1414 # first parent.
1413 if p1 != parents[0]:
1415 if p1 != parents[0]:
1414 p1 = parents[0]
1416 p1 = parents[0]
1415 p2 = repo[nullid]
1417 p2 = repo[nullid]
1416 except error.RepoError:
1418 except error.RepoError:
1417 p1, p2 = parents
1419 p1, p2 = parents
1418 if p2.node() == nullid:
1420 if p2.node() == nullid:
1419 ui.warn(_("warning: import the patch as a normal revision\n"
1421 ui.warn(_("warning: import the patch as a normal revision\n"
1420 "(use --exact to import the patch as a merge)\n"))
1422 "(use --exact to import the patch as a merge)\n"))
1421 else:
1423 else:
1422 p1, p2 = parents
1424 p1, p2 = parents
1423
1425
1424 n = None
1426 n = None
1425 if update:
1427 if update:
1426 if p1 != parents[0]:
1428 if p1 != parents[0]:
1427 updatefunc(repo, p1.node())
1429 updatefunc(repo, p1.node())
1428 if p2 != parents[1]:
1430 if p2 != parents[1]:
1429 repo.setparents(p1.node(), p2.node())
1431 repo.setparents(p1.node(), p2.node())
1430
1432
1431 if opts.get('exact') or importbranch:
1433 if opts.get('exact') or importbranch:
1432 repo.dirstate.setbranch(branch or 'default')
1434 repo.dirstate.setbranch(branch or 'default')
1433
1435
1434 partial = opts.get('partial', False)
1436 partial = opts.get('partial', False)
1435 files = set()
1437 files = set()
1436 try:
1438 try:
1437 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1439 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1438 files=files, eolmode=None, similarity=sim / 100.0)
1440 files=files, eolmode=None, similarity=sim / 100.0)
1439 except error.PatchError as e:
1441 except error.PatchError as e:
1440 if not partial:
1442 if not partial:
1441 raise error.Abort(pycompat.bytestr(e))
1443 raise error.Abort(pycompat.bytestr(e))
1442 if partial:
1444 if partial:
1443 rejects = True
1445 rejects = True
1444
1446
1445 files = list(files)
1447 files = list(files)
1446 if nocommit:
1448 if nocommit:
1447 if message:
1449 if message:
1448 msgs.append(message)
1450 msgs.append(message)
1449 else:
1451 else:
1450 if opts.get('exact') or p2:
1452 if opts.get('exact') or p2:
1451 # If you got here, you either use --force and know what
1453 # If you got here, you either use --force and know what
1452 # you are doing or used --exact or a merge patch while
1454 # you are doing or used --exact or a merge patch while
1453 # being updated to its first parent.
1455 # being updated to its first parent.
1454 m = None
1456 m = None
1455 else:
1457 else:
1456 m = scmutil.matchfiles(repo, files or [])
1458 m = scmutil.matchfiles(repo, files or [])
1457 editform = mergeeditform(repo[None], 'import.normal')
1459 editform = mergeeditform(repo[None], 'import.normal')
1458 if opts.get('exact'):
1460 if opts.get('exact'):
1459 editor = None
1461 editor = None
1460 else:
1462 else:
1461 editor = getcommiteditor(editform=editform,
1463 editor = getcommiteditor(editform=editform,
1462 **pycompat.strkwargs(opts))
1464 **pycompat.strkwargs(opts))
1463 extra = {}
1465 extra = {}
1464 for idfunc in extrapreimport:
1466 for idfunc in extrapreimport:
1465 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1467 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1466 overrides = {}
1468 overrides = {}
1467 if partial:
1469 if partial:
1468 overrides[('ui', 'allowemptycommit')] = True
1470 overrides[('ui', 'allowemptycommit')] = True
1469 with repo.ui.configoverride(overrides, 'import'):
1471 with repo.ui.configoverride(overrides, 'import'):
1470 n = repo.commit(message, user,
1472 n = repo.commit(message, user,
1471 date, match=m,
1473 date, match=m,
1472 editor=editor, extra=extra)
1474 editor=editor, extra=extra)
1473 for idfunc in extrapostimport:
1475 for idfunc in extrapostimport:
1474 extrapostimportmap[idfunc](repo[n])
1476 extrapostimportmap[idfunc](repo[n])
1475 else:
1477 else:
1476 if opts.get('exact') or importbranch:
1478 if opts.get('exact') or importbranch:
1477 branch = branch or 'default'
1479 branch = branch or 'default'
1478 else:
1480 else:
1479 branch = p1.branch()
1481 branch = p1.branch()
1480 store = patch.filestore()
1482 store = patch.filestore()
1481 try:
1483 try:
1482 files = set()
1484 files = set()
1483 try:
1485 try:
1484 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1486 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1485 files, eolmode=None)
1487 files, eolmode=None)
1486 except error.PatchError as e:
1488 except error.PatchError as e:
1487 raise error.Abort(stringutil.forcebytestr(e))
1489 raise error.Abort(stringutil.forcebytestr(e))
1488 if opts.get('exact'):
1490 if opts.get('exact'):
1489 editor = None
1491 editor = None
1490 else:
1492 else:
1491 editor = getcommiteditor(editform='import.bypass')
1493 editor = getcommiteditor(editform='import.bypass')
1492 memctx = context.memctx(repo, (p1.node(), p2.node()),
1494 memctx = context.memctx(repo, (p1.node(), p2.node()),
1493 message,
1495 message,
1494 files=files,
1496 files=files,
1495 filectxfn=store,
1497 filectxfn=store,
1496 user=user,
1498 user=user,
1497 date=date,
1499 date=date,
1498 branch=branch,
1500 branch=branch,
1499 editor=editor)
1501 editor=editor)
1500 n = memctx.commit()
1502 n = memctx.commit()
1501 finally:
1503 finally:
1502 store.close()
1504 store.close()
1503 if opts.get('exact') and nocommit:
1505 if opts.get('exact') and nocommit:
1504 # --exact with --no-commit is still useful in that it does merge
1506 # --exact with --no-commit is still useful in that it does merge
1505 # and branch bits
1507 # and branch bits
1506 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1508 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1507 elif opts.get('exact') and hex(n) != nodeid:
1509 elif opts.get('exact') and hex(n) != nodeid:
1508 raise error.Abort(_('patch is damaged or loses information'))
1510 raise error.Abort(_('patch is damaged or loses information'))
1509 msg = _('applied to working directory')
1511 msg = _('applied to working directory')
1510 if n:
1512 if n:
1511 # i18n: refers to a short changeset id
1513 # i18n: refers to a short changeset id
1512 msg = _('created %s') % short(n)
1514 msg = _('created %s') % short(n)
1513 return (msg, n, rejects)
1515 return (msg, n, rejects)
1514 finally:
1516 finally:
1515 os.unlink(tmpname)
1517 os.unlink(tmpname)
1516
1518
1517 # facility to let extensions include additional data in an exported patch
1519 # facility to let extensions include additional data in an exported patch
1518 # list of identifiers to be executed in order
1520 # list of identifiers to be executed in order
1519 extraexport = []
1521 extraexport = []
1520 # mapping from identifier to actual export function
1522 # mapping from identifier to actual export function
1521 # function as to return a string to be added to the header or None
1523 # function as to return a string to be added to the header or None
1522 # it is given two arguments (sequencenumber, changectx)
1524 # it is given two arguments (sequencenumber, changectx)
1523 extraexportmap = {}
1525 extraexportmap = {}
1524
1526
1525 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1527 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1526 node = scmutil.binnode(ctx)
1528 node = scmutil.binnode(ctx)
1527 parents = [p.node() for p in ctx.parents() if p]
1529 parents = [p.node() for p in ctx.parents() if p]
1528 branch = ctx.branch()
1530 branch = ctx.branch()
1529 if switch_parent:
1531 if switch_parent:
1530 parents.reverse()
1532 parents.reverse()
1531
1533
1532 if parents:
1534 if parents:
1533 prev = parents[0]
1535 prev = parents[0]
1534 else:
1536 else:
1535 prev = nullid
1537 prev = nullid
1536
1538
1537 write("# HG changeset patch\n")
1539 write("# HG changeset patch\n")
1538 write("# User %s\n" % ctx.user())
1540 write("# User %s\n" % ctx.user())
1539 write("# Date %d %d\n" % ctx.date())
1541 write("# Date %d %d\n" % ctx.date())
1540 write("# %s\n" % dateutil.datestr(ctx.date()))
1542 write("# %s\n" % dateutil.datestr(ctx.date()))
1541 if branch and branch != 'default':
1543 if branch and branch != 'default':
1542 write("# Branch %s\n" % branch)
1544 write("# Branch %s\n" % branch)
1543 write("# Node ID %s\n" % hex(node))
1545 write("# Node ID %s\n" % hex(node))
1544 write("# Parent %s\n" % hex(prev))
1546 write("# Parent %s\n" % hex(prev))
1545 if len(parents) > 1:
1547 if len(parents) > 1:
1546 write("# Parent %s\n" % hex(parents[1]))
1548 write("# Parent %s\n" % hex(parents[1]))
1547
1549
1548 for headerid in extraexport:
1550 for headerid in extraexport:
1549 header = extraexportmap[headerid](seqno, ctx)
1551 header = extraexportmap[headerid](seqno, ctx)
1550 if header is not None:
1552 if header is not None:
1551 write('# %s\n' % header)
1553 write('# %s\n' % header)
1552 write(ctx.description().rstrip())
1554 write(ctx.description().rstrip())
1553 write("\n\n")
1555 write("\n\n")
1554
1556
1555 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1557 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1556 write(chunk, label=label)
1558 write(chunk, label=label)
1557
1559
1558 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1560 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1559 opts=None, match=None):
1561 opts=None, match=None):
1560 '''export changesets as hg patches
1562 '''export changesets as hg patches
1561
1563
1562 Args:
1564 Args:
1563 repo: The repository from which we're exporting revisions.
1565 repo: The repository from which we're exporting revisions.
1564 revs: A list of revisions to export as revision numbers.
1566 revs: A list of revisions to export as revision numbers.
1565 fntemplate: An optional string to use for generating patch file names.
1567 fntemplate: An optional string to use for generating patch file names.
1566 fp: An optional file-like object to which patches should be written.
1568 fp: An optional file-like object to which patches should be written.
1567 switch_parent: If True, show diffs against second parent when not nullid.
1569 switch_parent: If True, show diffs against second parent when not nullid.
1568 Default is false, which always shows diff against p1.
1570 Default is false, which always shows diff against p1.
1569 opts: diff options to use for generating the patch.
1571 opts: diff options to use for generating the patch.
1570 match: If specified, only export changes to files matching this matcher.
1572 match: If specified, only export changes to files matching this matcher.
1571
1573
1572 Returns:
1574 Returns:
1573 Nothing.
1575 Nothing.
1574
1576
1575 Side Effect:
1577 Side Effect:
1576 "HG Changeset Patch" data is emitted to one of the following
1578 "HG Changeset Patch" data is emitted to one of the following
1577 destinations:
1579 destinations:
1578 fp is specified: All revs are written to the specified
1580 fp is specified: All revs are written to the specified
1579 file-like object.
1581 file-like object.
1580 fntemplate specified: Each rev is written to a unique file named using
1582 fntemplate specified: Each rev is written to a unique file named using
1581 the given template.
1583 the given template.
1582 Neither fp nor template specified: All revs written to repo.ui.write()
1584 Neither fp nor template specified: All revs written to repo.ui.write()
1583 '''
1585 '''
1584
1586
1585 total = len(revs)
1587 total = len(revs)
1586 revwidth = max(len(str(rev)) for rev in revs)
1588 revwidth = max(len(str(rev)) for rev in revs)
1587 filemode = {}
1589 filemode = {}
1588
1590
1589 write = None
1591 write = None
1590 dest = '<unnamed>'
1592 dest = '<unnamed>'
1591 if fp:
1593 if fp:
1592 dest = getattr(fp, 'name', dest)
1594 dest = getattr(fp, 'name', dest)
1593 def write(s, **kw):
1595 def write(s, **kw):
1594 fp.write(s)
1596 fp.write(s)
1595 elif not fntemplate:
1597 elif not fntemplate:
1596 write = repo.ui.write
1598 write = repo.ui.write
1597
1599
1598 for seqno, rev in enumerate(revs, 1):
1600 for seqno, rev in enumerate(revs, 1):
1599 ctx = repo[rev]
1601 ctx = repo[rev]
1600 fo = None
1602 fo = None
1601 if not fp and fntemplate:
1603 if not fp and fntemplate:
1602 fo = makefileobj(ctx, fntemplate, mode='wb', modemap=filemode,
1604 fo = makefileobj(ctx, fntemplate, mode='wb', modemap=filemode,
1603 total=total, seqno=seqno, revwidth=revwidth)
1605 total=total, seqno=seqno, revwidth=revwidth)
1604 dest = fo.name
1606 dest = fo.name
1605 def write(s, **kw):
1607 def write(s, **kw):
1606 fo.write(s)
1608 fo.write(s)
1607 if not dest.startswith('<'):
1609 if not dest.startswith('<'):
1608 repo.ui.note("%s\n" % dest)
1610 repo.ui.note("%s\n" % dest)
1609 _exportsingle(
1611 _exportsingle(
1610 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1612 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1611 if fo is not None:
1613 if fo is not None:
1612 fo.close()
1614 fo.close()
1613
1615
1614 def showmarker(fm, marker, index=None):
1616 def showmarker(fm, marker, index=None):
1615 """utility function to display obsolescence marker in a readable way
1617 """utility function to display obsolescence marker in a readable way
1616
1618
1617 To be used by debug function."""
1619 To be used by debug function."""
1618 if index is not None:
1620 if index is not None:
1619 fm.write('index', '%i ', index)
1621 fm.write('index', '%i ', index)
1620 fm.write('prednode', '%s ', hex(marker.prednode()))
1622 fm.write('prednode', '%s ', hex(marker.prednode()))
1621 succs = marker.succnodes()
1623 succs = marker.succnodes()
1622 fm.condwrite(succs, 'succnodes', '%s ',
1624 fm.condwrite(succs, 'succnodes', '%s ',
1623 fm.formatlist(map(hex, succs), name='node'))
1625 fm.formatlist(map(hex, succs), name='node'))
1624 fm.write('flag', '%X ', marker.flags())
1626 fm.write('flag', '%X ', marker.flags())
1625 parents = marker.parentnodes()
1627 parents = marker.parentnodes()
1626 if parents is not None:
1628 if parents is not None:
1627 fm.write('parentnodes', '{%s} ',
1629 fm.write('parentnodes', '{%s} ',
1628 fm.formatlist(map(hex, parents), name='node', sep=', '))
1630 fm.formatlist(map(hex, parents), name='node', sep=', '))
1629 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1631 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1630 meta = marker.metadata().copy()
1632 meta = marker.metadata().copy()
1631 meta.pop('date', None)
1633 meta.pop('date', None)
1632 smeta = util.rapply(pycompat.maybebytestr, meta)
1634 smeta = util.rapply(pycompat.maybebytestr, meta)
1633 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1635 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1634 fm.plain('\n')
1636 fm.plain('\n')
1635
1637
1636 def finddate(ui, repo, date):
1638 def finddate(ui, repo, date):
1637 """Find the tipmost changeset that matches the given date spec"""
1639 """Find the tipmost changeset that matches the given date spec"""
1638
1640
1639 df = dateutil.matchdate(date)
1641 df = dateutil.matchdate(date)
1640 m = scmutil.matchall(repo)
1642 m = scmutil.matchall(repo)
1641 results = {}
1643 results = {}
1642
1644
1643 def prep(ctx, fns):
1645 def prep(ctx, fns):
1644 d = ctx.date()
1646 d = ctx.date()
1645 if df(d[0]):
1647 if df(d[0]):
1646 results[ctx.rev()] = d
1648 results[ctx.rev()] = d
1647
1649
1648 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1650 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1649 rev = ctx.rev()
1651 rev = ctx.rev()
1650 if rev in results:
1652 if rev in results:
1651 ui.status(_("found revision %s from %s\n") %
1653 ui.status(_("found revision %s from %s\n") %
1652 (rev, dateutil.datestr(results[rev])))
1654 (rev, dateutil.datestr(results[rev])))
1653 return '%d' % rev
1655 return '%d' % rev
1654
1656
1655 raise error.Abort(_("revision matching date not found"))
1657 raise error.Abort(_("revision matching date not found"))
1656
1658
1657 def increasingwindows(windowsize=8, sizelimit=512):
1659 def increasingwindows(windowsize=8, sizelimit=512):
1658 while True:
1660 while True:
1659 yield windowsize
1661 yield windowsize
1660 if windowsize < sizelimit:
1662 if windowsize < sizelimit:
1661 windowsize *= 2
1663 windowsize *= 2
1662
1664
1663 def _walkrevs(repo, opts):
1665 def _walkrevs(repo, opts):
1664 # Default --rev value depends on --follow but --follow behavior
1666 # Default --rev value depends on --follow but --follow behavior
1665 # depends on revisions resolved from --rev...
1667 # depends on revisions resolved from --rev...
1666 follow = opts.get('follow') or opts.get('follow_first')
1668 follow = opts.get('follow') or opts.get('follow_first')
1667 if opts.get('rev'):
1669 if opts.get('rev'):
1668 revs = scmutil.revrange(repo, opts['rev'])
1670 revs = scmutil.revrange(repo, opts['rev'])
1669 elif follow and repo.dirstate.p1() == nullid:
1671 elif follow and repo.dirstate.p1() == nullid:
1670 revs = smartset.baseset()
1672 revs = smartset.baseset()
1671 elif follow:
1673 elif follow:
1672 revs = repo.revs('reverse(:.)')
1674 revs = repo.revs('reverse(:.)')
1673 else:
1675 else:
1674 revs = smartset.spanset(repo)
1676 revs = smartset.spanset(repo)
1675 revs.reverse()
1677 revs.reverse()
1676 return revs
1678 return revs
1677
1679
1678 class FileWalkError(Exception):
1680 class FileWalkError(Exception):
1679 pass
1681 pass
1680
1682
1681 def walkfilerevs(repo, match, follow, revs, fncache):
1683 def walkfilerevs(repo, match, follow, revs, fncache):
1682 '''Walks the file history for the matched files.
1684 '''Walks the file history for the matched files.
1683
1685
1684 Returns the changeset revs that are involved in the file history.
1686 Returns the changeset revs that are involved in the file history.
1685
1687
1686 Throws FileWalkError if the file history can't be walked using
1688 Throws FileWalkError if the file history can't be walked using
1687 filelogs alone.
1689 filelogs alone.
1688 '''
1690 '''
1689 wanted = set()
1691 wanted = set()
1690 copies = []
1692 copies = []
1691 minrev, maxrev = min(revs), max(revs)
1693 minrev, maxrev = min(revs), max(revs)
1692 def filerevgen(filelog, last):
1694 def filerevgen(filelog, last):
1693 """
1695 """
1694 Only files, no patterns. Check the history of each file.
1696 Only files, no patterns. Check the history of each file.
1695
1697
1696 Examines filelog entries within minrev, maxrev linkrev range
1698 Examines filelog entries within minrev, maxrev linkrev range
1697 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1699 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1698 tuples in backwards order
1700 tuples in backwards order
1699 """
1701 """
1700 cl_count = len(repo)
1702 cl_count = len(repo)
1701 revs = []
1703 revs = []
1702 for j in xrange(0, last + 1):
1704 for j in xrange(0, last + 1):
1703 linkrev = filelog.linkrev(j)
1705 linkrev = filelog.linkrev(j)
1704 if linkrev < minrev:
1706 if linkrev < minrev:
1705 continue
1707 continue
1706 # only yield rev for which we have the changelog, it can
1708 # only yield rev for which we have the changelog, it can
1707 # happen while doing "hg log" during a pull or commit
1709 # happen while doing "hg log" during a pull or commit
1708 if linkrev >= cl_count:
1710 if linkrev >= cl_count:
1709 break
1711 break
1710
1712
1711 parentlinkrevs = []
1713 parentlinkrevs = []
1712 for p in filelog.parentrevs(j):
1714 for p in filelog.parentrevs(j):
1713 if p != nullrev:
1715 if p != nullrev:
1714 parentlinkrevs.append(filelog.linkrev(p))
1716 parentlinkrevs.append(filelog.linkrev(p))
1715 n = filelog.node(j)
1717 n = filelog.node(j)
1716 revs.append((linkrev, parentlinkrevs,
1718 revs.append((linkrev, parentlinkrevs,
1717 follow and filelog.renamed(n)))
1719 follow and filelog.renamed(n)))
1718
1720
1719 return reversed(revs)
1721 return reversed(revs)
1720 def iterfiles():
1722 def iterfiles():
1721 pctx = repo['.']
1723 pctx = repo['.']
1722 for filename in match.files():
1724 for filename in match.files():
1723 if follow:
1725 if follow:
1724 if filename not in pctx:
1726 if filename not in pctx:
1725 raise error.Abort(_('cannot follow file not in parent '
1727 raise error.Abort(_('cannot follow file not in parent '
1726 'revision: "%s"') % filename)
1728 'revision: "%s"') % filename)
1727 yield filename, pctx[filename].filenode()
1729 yield filename, pctx[filename].filenode()
1728 else:
1730 else:
1729 yield filename, None
1731 yield filename, None
1730 for filename_node in copies:
1732 for filename_node in copies:
1731 yield filename_node
1733 yield filename_node
1732
1734
1733 for file_, node in iterfiles():
1735 for file_, node in iterfiles():
1734 filelog = repo.file(file_)
1736 filelog = repo.file(file_)
1735 if not len(filelog):
1737 if not len(filelog):
1736 if node is None:
1738 if node is None:
1737 # A zero count may be a directory or deleted file, so
1739 # A zero count may be a directory or deleted file, so
1738 # try to find matching entries on the slow path.
1740 # try to find matching entries on the slow path.
1739 if follow:
1741 if follow:
1740 raise error.Abort(
1742 raise error.Abort(
1741 _('cannot follow nonexistent file: "%s"') % file_)
1743 _('cannot follow nonexistent file: "%s"') % file_)
1742 raise FileWalkError("Cannot walk via filelog")
1744 raise FileWalkError("Cannot walk via filelog")
1743 else:
1745 else:
1744 continue
1746 continue
1745
1747
1746 if node is None:
1748 if node is None:
1747 last = len(filelog) - 1
1749 last = len(filelog) - 1
1748 else:
1750 else:
1749 last = filelog.rev(node)
1751 last = filelog.rev(node)
1750
1752
1751 # keep track of all ancestors of the file
1753 # keep track of all ancestors of the file
1752 ancestors = {filelog.linkrev(last)}
1754 ancestors = {filelog.linkrev(last)}
1753
1755
1754 # iterate from latest to oldest revision
1756 # iterate from latest to oldest revision
1755 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1757 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1756 if not follow:
1758 if not follow:
1757 if rev > maxrev:
1759 if rev > maxrev:
1758 continue
1760 continue
1759 else:
1761 else:
1760 # Note that last might not be the first interesting
1762 # Note that last might not be the first interesting
1761 # rev to us:
1763 # rev to us:
1762 # if the file has been changed after maxrev, we'll
1764 # if the file has been changed after maxrev, we'll
1763 # have linkrev(last) > maxrev, and we still need
1765 # have linkrev(last) > maxrev, and we still need
1764 # to explore the file graph
1766 # to explore the file graph
1765 if rev not in ancestors:
1767 if rev not in ancestors:
1766 continue
1768 continue
1767 # XXX insert 1327 fix here
1769 # XXX insert 1327 fix here
1768 if flparentlinkrevs:
1770 if flparentlinkrevs:
1769 ancestors.update(flparentlinkrevs)
1771 ancestors.update(flparentlinkrevs)
1770
1772
1771 fncache.setdefault(rev, []).append(file_)
1773 fncache.setdefault(rev, []).append(file_)
1772 wanted.add(rev)
1774 wanted.add(rev)
1773 if copied:
1775 if copied:
1774 copies.append(copied)
1776 copies.append(copied)
1775
1777
1776 return wanted
1778 return wanted
1777
1779
1778 class _followfilter(object):
1780 class _followfilter(object):
1779 def __init__(self, repo, onlyfirst=False):
1781 def __init__(self, repo, onlyfirst=False):
1780 self.repo = repo
1782 self.repo = repo
1781 self.startrev = nullrev
1783 self.startrev = nullrev
1782 self.roots = set()
1784 self.roots = set()
1783 self.onlyfirst = onlyfirst
1785 self.onlyfirst = onlyfirst
1784
1786
1785 def match(self, rev):
1787 def match(self, rev):
1786 def realparents(rev):
1788 def realparents(rev):
1787 if self.onlyfirst:
1789 if self.onlyfirst:
1788 return self.repo.changelog.parentrevs(rev)[0:1]
1790 return self.repo.changelog.parentrevs(rev)[0:1]
1789 else:
1791 else:
1790 return filter(lambda x: x != nullrev,
1792 return filter(lambda x: x != nullrev,
1791 self.repo.changelog.parentrevs(rev))
1793 self.repo.changelog.parentrevs(rev))
1792
1794
1793 if self.startrev == nullrev:
1795 if self.startrev == nullrev:
1794 self.startrev = rev
1796 self.startrev = rev
1795 return True
1797 return True
1796
1798
1797 if rev > self.startrev:
1799 if rev > self.startrev:
1798 # forward: all descendants
1800 # forward: all descendants
1799 if not self.roots:
1801 if not self.roots:
1800 self.roots.add(self.startrev)
1802 self.roots.add(self.startrev)
1801 for parent in realparents(rev):
1803 for parent in realparents(rev):
1802 if parent in self.roots:
1804 if parent in self.roots:
1803 self.roots.add(rev)
1805 self.roots.add(rev)
1804 return True
1806 return True
1805 else:
1807 else:
1806 # backwards: all parents
1808 # backwards: all parents
1807 if not self.roots:
1809 if not self.roots:
1808 self.roots.update(realparents(self.startrev))
1810 self.roots.update(realparents(self.startrev))
1809 if rev in self.roots:
1811 if rev in self.roots:
1810 self.roots.remove(rev)
1812 self.roots.remove(rev)
1811 self.roots.update(realparents(rev))
1813 self.roots.update(realparents(rev))
1812 return True
1814 return True
1813
1815
1814 return False
1816 return False
1815
1817
1816 def walkchangerevs(repo, match, opts, prepare):
1818 def walkchangerevs(repo, match, opts, prepare):
1817 '''Iterate over files and the revs in which they changed.
1819 '''Iterate over files and the revs in which they changed.
1818
1820
1819 Callers most commonly need to iterate backwards over the history
1821 Callers most commonly need to iterate backwards over the history
1820 in which they are interested. Doing so has awful (quadratic-looking)
1822 in which they are interested. Doing so has awful (quadratic-looking)
1821 performance, so we use iterators in a "windowed" way.
1823 performance, so we use iterators in a "windowed" way.
1822
1824
1823 We walk a window of revisions in the desired order. Within the
1825 We walk a window of revisions in the desired order. Within the
1824 window, we first walk forwards to gather data, then in the desired
1826 window, we first walk forwards to gather data, then in the desired
1825 order (usually backwards) to display it.
1827 order (usually backwards) to display it.
1826
1828
1827 This function returns an iterator yielding contexts. Before
1829 This function returns an iterator yielding contexts. Before
1828 yielding each context, the iterator will first call the prepare
1830 yielding each context, the iterator will first call the prepare
1829 function on each context in the window in forward order.'''
1831 function on each context in the window in forward order.'''
1830
1832
1831 follow = opts.get('follow') or opts.get('follow_first')
1833 follow = opts.get('follow') or opts.get('follow_first')
1832 revs = _walkrevs(repo, opts)
1834 revs = _walkrevs(repo, opts)
1833 if not revs:
1835 if not revs:
1834 return []
1836 return []
1835 wanted = set()
1837 wanted = set()
1836 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1838 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1837 fncache = {}
1839 fncache = {}
1838 change = repo.changectx
1840 change = repo.changectx
1839
1841
1840 # First step is to fill wanted, the set of revisions that we want to yield.
1842 # First step is to fill wanted, the set of revisions that we want to yield.
1841 # When it does not induce extra cost, we also fill fncache for revisions in
1843 # When it does not induce extra cost, we also fill fncache for revisions in
1842 # wanted: a cache of filenames that were changed (ctx.files()) and that
1844 # wanted: a cache of filenames that were changed (ctx.files()) and that
1843 # match the file filtering conditions.
1845 # match the file filtering conditions.
1844
1846
1845 if match.always():
1847 if match.always():
1846 # No files, no patterns. Display all revs.
1848 # No files, no patterns. Display all revs.
1847 wanted = revs
1849 wanted = revs
1848 elif not slowpath:
1850 elif not slowpath:
1849 # We only have to read through the filelog to find wanted revisions
1851 # We only have to read through the filelog to find wanted revisions
1850
1852
1851 try:
1853 try:
1852 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1854 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1853 except FileWalkError:
1855 except FileWalkError:
1854 slowpath = True
1856 slowpath = True
1855
1857
1856 # We decided to fall back to the slowpath because at least one
1858 # We decided to fall back to the slowpath because at least one
1857 # of the paths was not a file. Check to see if at least one of them
1859 # of the paths was not a file. Check to see if at least one of them
1858 # existed in history, otherwise simply return
1860 # existed in history, otherwise simply return
1859 for path in match.files():
1861 for path in match.files():
1860 if path == '.' or path in repo.store:
1862 if path == '.' or path in repo.store:
1861 break
1863 break
1862 else:
1864 else:
1863 return []
1865 return []
1864
1866
1865 if slowpath:
1867 if slowpath:
1866 # We have to read the changelog to match filenames against
1868 # We have to read the changelog to match filenames against
1867 # changed files
1869 # changed files
1868
1870
1869 if follow:
1871 if follow:
1870 raise error.Abort(_('can only follow copies/renames for explicit '
1872 raise error.Abort(_('can only follow copies/renames for explicit '
1871 'filenames'))
1873 'filenames'))
1872
1874
1873 # The slow path checks files modified in every changeset.
1875 # The slow path checks files modified in every changeset.
1874 # This is really slow on large repos, so compute the set lazily.
1876 # This is really slow on large repos, so compute the set lazily.
1875 class lazywantedset(object):
1877 class lazywantedset(object):
1876 def __init__(self):
1878 def __init__(self):
1877 self.set = set()
1879 self.set = set()
1878 self.revs = set(revs)
1880 self.revs = set(revs)
1879
1881
1880 # No need to worry about locality here because it will be accessed
1882 # No need to worry about locality here because it will be accessed
1881 # in the same order as the increasing window below.
1883 # in the same order as the increasing window below.
1882 def __contains__(self, value):
1884 def __contains__(self, value):
1883 if value in self.set:
1885 if value in self.set:
1884 return True
1886 return True
1885 elif not value in self.revs:
1887 elif not value in self.revs:
1886 return False
1888 return False
1887 else:
1889 else:
1888 self.revs.discard(value)
1890 self.revs.discard(value)
1889 ctx = change(value)
1891 ctx = change(value)
1890 matches = [f for f in ctx.files() if match(f)]
1892 matches = [f for f in ctx.files() if match(f)]
1891 if matches:
1893 if matches:
1892 fncache[value] = matches
1894 fncache[value] = matches
1893 self.set.add(value)
1895 self.set.add(value)
1894 return True
1896 return True
1895 return False
1897 return False
1896
1898
1897 def discard(self, value):
1899 def discard(self, value):
1898 self.revs.discard(value)
1900 self.revs.discard(value)
1899 self.set.discard(value)
1901 self.set.discard(value)
1900
1902
1901 wanted = lazywantedset()
1903 wanted = lazywantedset()
1902
1904
1903 # it might be worthwhile to do this in the iterator if the rev range
1905 # it might be worthwhile to do this in the iterator if the rev range
1904 # is descending and the prune args are all within that range
1906 # is descending and the prune args are all within that range
1905 for rev in opts.get('prune', ()):
1907 for rev in opts.get('prune', ()):
1906 rev = repo[rev].rev()
1908 rev = repo[rev].rev()
1907 ff = _followfilter(repo)
1909 ff = _followfilter(repo)
1908 stop = min(revs[0], revs[-1])
1910 stop = min(revs[0], revs[-1])
1909 for x in xrange(rev, stop - 1, -1):
1911 for x in xrange(rev, stop - 1, -1):
1910 if ff.match(x):
1912 if ff.match(x):
1911 wanted = wanted - [x]
1913 wanted = wanted - [x]
1912
1914
1913 # Now that wanted is correctly initialized, we can iterate over the
1915 # Now that wanted is correctly initialized, we can iterate over the
1914 # revision range, yielding only revisions in wanted.
1916 # revision range, yielding only revisions in wanted.
1915 def iterate():
1917 def iterate():
1916 if follow and match.always():
1918 if follow and match.always():
1917 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1919 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1918 def want(rev):
1920 def want(rev):
1919 return ff.match(rev) and rev in wanted
1921 return ff.match(rev) and rev in wanted
1920 else:
1922 else:
1921 def want(rev):
1923 def want(rev):
1922 return rev in wanted
1924 return rev in wanted
1923
1925
1924 it = iter(revs)
1926 it = iter(revs)
1925 stopiteration = False
1927 stopiteration = False
1926 for windowsize in increasingwindows():
1928 for windowsize in increasingwindows():
1927 nrevs = []
1929 nrevs = []
1928 for i in xrange(windowsize):
1930 for i in xrange(windowsize):
1929 rev = next(it, None)
1931 rev = next(it, None)
1930 if rev is None:
1932 if rev is None:
1931 stopiteration = True
1933 stopiteration = True
1932 break
1934 break
1933 elif want(rev):
1935 elif want(rev):
1934 nrevs.append(rev)
1936 nrevs.append(rev)
1935 for rev in sorted(nrevs):
1937 for rev in sorted(nrevs):
1936 fns = fncache.get(rev)
1938 fns = fncache.get(rev)
1937 ctx = change(rev)
1939 ctx = change(rev)
1938 if not fns:
1940 if not fns:
1939 def fns_generator():
1941 def fns_generator():
1940 for f in ctx.files():
1942 for f in ctx.files():
1941 if match(f):
1943 if match(f):
1942 yield f
1944 yield f
1943 fns = fns_generator()
1945 fns = fns_generator()
1944 prepare(ctx, fns)
1946 prepare(ctx, fns)
1945 for rev in nrevs:
1947 for rev in nrevs:
1946 yield change(rev)
1948 yield change(rev)
1947
1949
1948 if stopiteration:
1950 if stopiteration:
1949 break
1951 break
1950
1952
1951 return iterate()
1953 return iterate()
1952
1954
1953 def add(ui, repo, match, prefix, explicitonly, **opts):
1955 def add(ui, repo, match, prefix, explicitonly, **opts):
1954 join = lambda f: os.path.join(prefix, f)
1956 join = lambda f: os.path.join(prefix, f)
1955 bad = []
1957 bad = []
1956
1958
1957 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1959 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1958 names = []
1960 names = []
1959 wctx = repo[None]
1961 wctx = repo[None]
1960 cca = None
1962 cca = None
1961 abort, warn = scmutil.checkportabilityalert(ui)
1963 abort, warn = scmutil.checkportabilityalert(ui)
1962 if abort or warn:
1964 if abort or warn:
1963 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1965 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1964
1966
1965 badmatch = matchmod.badmatch(match, badfn)
1967 badmatch = matchmod.badmatch(match, badfn)
1966 dirstate = repo.dirstate
1968 dirstate = repo.dirstate
1967 # We don't want to just call wctx.walk here, since it would return a lot of
1969 # We don't want to just call wctx.walk here, since it would return a lot of
1968 # clean files, which we aren't interested in and takes time.
1970 # clean files, which we aren't interested in and takes time.
1969 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
1971 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
1970 unknown=True, ignored=False, full=False)):
1972 unknown=True, ignored=False, full=False)):
1971 exact = match.exact(f)
1973 exact = match.exact(f)
1972 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1974 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1973 if cca:
1975 if cca:
1974 cca(f)
1976 cca(f)
1975 names.append(f)
1977 names.append(f)
1976 if ui.verbose or not exact:
1978 if ui.verbose or not exact:
1977 ui.status(_('adding %s\n') % match.rel(f))
1979 ui.status(_('adding %s\n') % match.rel(f))
1978
1980
1979 for subpath in sorted(wctx.substate):
1981 for subpath in sorted(wctx.substate):
1980 sub = wctx.sub(subpath)
1982 sub = wctx.sub(subpath)
1981 try:
1983 try:
1982 submatch = matchmod.subdirmatcher(subpath, match)
1984 submatch = matchmod.subdirmatcher(subpath, match)
1983 if opts.get(r'subrepos'):
1985 if opts.get(r'subrepos'):
1984 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1986 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1985 else:
1987 else:
1986 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1988 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1987 except error.LookupError:
1989 except error.LookupError:
1988 ui.status(_("skipping missing subrepository: %s\n")
1990 ui.status(_("skipping missing subrepository: %s\n")
1989 % join(subpath))
1991 % join(subpath))
1990
1992
1991 if not opts.get(r'dry_run'):
1993 if not opts.get(r'dry_run'):
1992 rejected = wctx.add(names, prefix)
1994 rejected = wctx.add(names, prefix)
1993 bad.extend(f for f in rejected if f in match.files())
1995 bad.extend(f for f in rejected if f in match.files())
1994 return bad
1996 return bad
1995
1997
1996 def addwebdirpath(repo, serverpath, webconf):
1998 def addwebdirpath(repo, serverpath, webconf):
1997 webconf[serverpath] = repo.root
1999 webconf[serverpath] = repo.root
1998 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2000 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
1999
2001
2000 for r in repo.revs('filelog("path:.hgsub")'):
2002 for r in repo.revs('filelog("path:.hgsub")'):
2001 ctx = repo[r]
2003 ctx = repo[r]
2002 for subpath in ctx.substate:
2004 for subpath in ctx.substate:
2003 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2005 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2004
2006
2005 def forget(ui, repo, match, prefix, explicitonly, dryrun):
2007 def forget(ui, repo, match, prefix, explicitonly, dryrun):
2006 join = lambda f: os.path.join(prefix, f)
2008 join = lambda f: os.path.join(prefix, f)
2007 bad = []
2009 bad = []
2008 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2010 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2009 wctx = repo[None]
2011 wctx = repo[None]
2010 forgot = []
2012 forgot = []
2011
2013
2012 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2014 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2013 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2015 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2014 if explicitonly:
2016 if explicitonly:
2015 forget = [f for f in forget if match.exact(f)]
2017 forget = [f for f in forget if match.exact(f)]
2016
2018
2017 for subpath in sorted(wctx.substate):
2019 for subpath in sorted(wctx.substate):
2018 sub = wctx.sub(subpath)
2020 sub = wctx.sub(subpath)
2019 try:
2021 try:
2020 submatch = matchmod.subdirmatcher(subpath, match)
2022 submatch = matchmod.subdirmatcher(subpath, match)
2021 subbad, subforgot = sub.forget(submatch, prefix, dryrun=dryrun)
2023 subbad, subforgot = sub.forget(submatch, prefix, dryrun=dryrun)
2022 bad.extend([subpath + '/' + f for f in subbad])
2024 bad.extend([subpath + '/' + f for f in subbad])
2023 forgot.extend([subpath + '/' + f for f in subforgot])
2025 forgot.extend([subpath + '/' + f for f in subforgot])
2024 except error.LookupError:
2026 except error.LookupError:
2025 ui.status(_("skipping missing subrepository: %s\n")
2027 ui.status(_("skipping missing subrepository: %s\n")
2026 % join(subpath))
2028 % join(subpath))
2027
2029
2028 if not explicitonly:
2030 if not explicitonly:
2029 for f in match.files():
2031 for f in match.files():
2030 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2032 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2031 if f not in forgot:
2033 if f not in forgot:
2032 if repo.wvfs.exists(f):
2034 if repo.wvfs.exists(f):
2033 # Don't complain if the exact case match wasn't given.
2035 # Don't complain if the exact case match wasn't given.
2034 # But don't do this until after checking 'forgot', so
2036 # But don't do this until after checking 'forgot', so
2035 # that subrepo files aren't normalized, and this op is
2037 # that subrepo files aren't normalized, and this op is
2036 # purely from data cached by the status walk above.
2038 # purely from data cached by the status walk above.
2037 if repo.dirstate.normalize(f) in repo.dirstate:
2039 if repo.dirstate.normalize(f) in repo.dirstate:
2038 continue
2040 continue
2039 ui.warn(_('not removing %s: '
2041 ui.warn(_('not removing %s: '
2040 'file is already untracked\n')
2042 'file is already untracked\n')
2041 % match.rel(f))
2043 % match.rel(f))
2042 bad.append(f)
2044 bad.append(f)
2043
2045
2044 for f in forget:
2046 for f in forget:
2045 if ui.verbose or not match.exact(f):
2047 if ui.verbose or not match.exact(f):
2046 ui.status(_('removing %s\n') % match.rel(f))
2048 ui.status(_('removing %s\n') % match.rel(f))
2047
2049
2048 if not dryrun:
2050 if not dryrun:
2049 rejected = wctx.forget(forget, prefix)
2051 rejected = wctx.forget(forget, prefix)
2050 bad.extend(f for f in rejected if f in match.files())
2052 bad.extend(f for f in rejected if f in match.files())
2051 forgot.extend(f for f in forget if f not in rejected)
2053 forgot.extend(f for f in forget if f not in rejected)
2052 return bad, forgot
2054 return bad, forgot
2053
2055
2054 def files(ui, ctx, m, fm, fmt, subrepos):
2056 def files(ui, ctx, m, fm, fmt, subrepos):
2055 rev = ctx.rev()
2057 rev = ctx.rev()
2056 ret = 1
2058 ret = 1
2057 ds = ctx.repo().dirstate
2059 ds = ctx.repo().dirstate
2058
2060
2059 for f in ctx.matches(m):
2061 for f in ctx.matches(m):
2060 if rev is None and ds[f] == 'r':
2062 if rev is None and ds[f] == 'r':
2061 continue
2063 continue
2062 fm.startitem()
2064 fm.startitem()
2063 if ui.verbose:
2065 if ui.verbose:
2064 fc = ctx[f]
2066 fc = ctx[f]
2065 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2067 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2066 fm.data(abspath=f)
2068 fm.data(abspath=f)
2067 fm.write('path', fmt, m.rel(f))
2069 fm.write('path', fmt, m.rel(f))
2068 ret = 0
2070 ret = 0
2069
2071
2070 for subpath in sorted(ctx.substate):
2072 for subpath in sorted(ctx.substate):
2071 submatch = matchmod.subdirmatcher(subpath, m)
2073 submatch = matchmod.subdirmatcher(subpath, m)
2072 if (subrepos or m.exact(subpath) or any(submatch.files())):
2074 if (subrepos or m.exact(subpath) or any(submatch.files())):
2073 sub = ctx.sub(subpath)
2075 sub = ctx.sub(subpath)
2074 try:
2076 try:
2075 recurse = m.exact(subpath) or subrepos
2077 recurse = m.exact(subpath) or subrepos
2076 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2078 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2077 ret = 0
2079 ret = 0
2078 except error.LookupError:
2080 except error.LookupError:
2079 ui.status(_("skipping missing subrepository: %s\n")
2081 ui.status(_("skipping missing subrepository: %s\n")
2080 % m.abs(subpath))
2082 % m.abs(subpath))
2081
2083
2082 return ret
2084 return ret
2083
2085
2084 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2086 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2085 join = lambda f: os.path.join(prefix, f)
2087 join = lambda f: os.path.join(prefix, f)
2086 ret = 0
2088 ret = 0
2087 s = repo.status(match=m, clean=True)
2089 s = repo.status(match=m, clean=True)
2088 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2090 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2089
2091
2090 wctx = repo[None]
2092 wctx = repo[None]
2091
2093
2092 if warnings is None:
2094 if warnings is None:
2093 warnings = []
2095 warnings = []
2094 warn = True
2096 warn = True
2095 else:
2097 else:
2096 warn = False
2098 warn = False
2097
2099
2098 subs = sorted(wctx.substate)
2100 subs = sorted(wctx.substate)
2099 total = len(subs)
2101 total = len(subs)
2100 count = 0
2102 count = 0
2101 for subpath in subs:
2103 for subpath in subs:
2102 count += 1
2104 count += 1
2103 submatch = matchmod.subdirmatcher(subpath, m)
2105 submatch = matchmod.subdirmatcher(subpath, m)
2104 if subrepos or m.exact(subpath) or any(submatch.files()):
2106 if subrepos or m.exact(subpath) or any(submatch.files()):
2105 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2107 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2106 sub = wctx.sub(subpath)
2108 sub = wctx.sub(subpath)
2107 try:
2109 try:
2108 if sub.removefiles(submatch, prefix, after, force, subrepos,
2110 if sub.removefiles(submatch, prefix, after, force, subrepos,
2109 warnings):
2111 warnings):
2110 ret = 1
2112 ret = 1
2111 except error.LookupError:
2113 except error.LookupError:
2112 warnings.append(_("skipping missing subrepository: %s\n")
2114 warnings.append(_("skipping missing subrepository: %s\n")
2113 % join(subpath))
2115 % join(subpath))
2114 ui.progress(_('searching'), None)
2116 ui.progress(_('searching'), None)
2115
2117
2116 # warn about failure to delete explicit files/dirs
2118 # warn about failure to delete explicit files/dirs
2117 deleteddirs = util.dirs(deleted)
2119 deleteddirs = util.dirs(deleted)
2118 files = m.files()
2120 files = m.files()
2119 total = len(files)
2121 total = len(files)
2120 count = 0
2122 count = 0
2121 for f in files:
2123 for f in files:
2122 def insubrepo():
2124 def insubrepo():
2123 for subpath in wctx.substate:
2125 for subpath in wctx.substate:
2124 if f.startswith(subpath + '/'):
2126 if f.startswith(subpath + '/'):
2125 return True
2127 return True
2126 return False
2128 return False
2127
2129
2128 count += 1
2130 count += 1
2129 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2131 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2130 isdir = f in deleteddirs or wctx.hasdir(f)
2132 isdir = f in deleteddirs or wctx.hasdir(f)
2131 if (f in repo.dirstate or isdir or f == '.'
2133 if (f in repo.dirstate or isdir or f == '.'
2132 or insubrepo() or f in subs):
2134 or insubrepo() or f in subs):
2133 continue
2135 continue
2134
2136
2135 if repo.wvfs.exists(f):
2137 if repo.wvfs.exists(f):
2136 if repo.wvfs.isdir(f):
2138 if repo.wvfs.isdir(f):
2137 warnings.append(_('not removing %s: no tracked files\n')
2139 warnings.append(_('not removing %s: no tracked files\n')
2138 % m.rel(f))
2140 % m.rel(f))
2139 else:
2141 else:
2140 warnings.append(_('not removing %s: file is untracked\n')
2142 warnings.append(_('not removing %s: file is untracked\n')
2141 % m.rel(f))
2143 % m.rel(f))
2142 # missing files will generate a warning elsewhere
2144 # missing files will generate a warning elsewhere
2143 ret = 1
2145 ret = 1
2144 ui.progress(_('deleting'), None)
2146 ui.progress(_('deleting'), None)
2145
2147
2146 if force:
2148 if force:
2147 list = modified + deleted + clean + added
2149 list = modified + deleted + clean + added
2148 elif after:
2150 elif after:
2149 list = deleted
2151 list = deleted
2150 remaining = modified + added + clean
2152 remaining = modified + added + clean
2151 total = len(remaining)
2153 total = len(remaining)
2152 count = 0
2154 count = 0
2153 for f in remaining:
2155 for f in remaining:
2154 count += 1
2156 count += 1
2155 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2157 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2156 if ui.verbose or (f in files):
2158 if ui.verbose or (f in files):
2157 warnings.append(_('not removing %s: file still exists\n')
2159 warnings.append(_('not removing %s: file still exists\n')
2158 % m.rel(f))
2160 % m.rel(f))
2159 ret = 1
2161 ret = 1
2160 ui.progress(_('skipping'), None)
2162 ui.progress(_('skipping'), None)
2161 else:
2163 else:
2162 list = deleted + clean
2164 list = deleted + clean
2163 total = len(modified) + len(added)
2165 total = len(modified) + len(added)
2164 count = 0
2166 count = 0
2165 for f in modified:
2167 for f in modified:
2166 count += 1
2168 count += 1
2167 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2169 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2168 warnings.append(_('not removing %s: file is modified (use -f'
2170 warnings.append(_('not removing %s: file is modified (use -f'
2169 ' to force removal)\n') % m.rel(f))
2171 ' to force removal)\n') % m.rel(f))
2170 ret = 1
2172 ret = 1
2171 for f in added:
2173 for f in added:
2172 count += 1
2174 count += 1
2173 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2175 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2174 warnings.append(_("not removing %s: file has been marked for add"
2176 warnings.append(_("not removing %s: file has been marked for add"
2175 " (use 'hg forget' to undo add)\n") % m.rel(f))
2177 " (use 'hg forget' to undo add)\n") % m.rel(f))
2176 ret = 1
2178 ret = 1
2177 ui.progress(_('skipping'), None)
2179 ui.progress(_('skipping'), None)
2178
2180
2179 list = sorted(list)
2181 list = sorted(list)
2180 total = len(list)
2182 total = len(list)
2181 count = 0
2183 count = 0
2182 for f in list:
2184 for f in list:
2183 count += 1
2185 count += 1
2184 if ui.verbose or not m.exact(f):
2186 if ui.verbose or not m.exact(f):
2185 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2187 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2186 ui.status(_('removing %s\n') % m.rel(f))
2188 ui.status(_('removing %s\n') % m.rel(f))
2187 ui.progress(_('deleting'), None)
2189 ui.progress(_('deleting'), None)
2188
2190
2189 with repo.wlock():
2191 with repo.wlock():
2190 if not after:
2192 if not after:
2191 for f in list:
2193 for f in list:
2192 if f in added:
2194 if f in added:
2193 continue # we never unlink added files on remove
2195 continue # we never unlink added files on remove
2194 repo.wvfs.unlinkpath(f, ignoremissing=True)
2196 repo.wvfs.unlinkpath(f, ignoremissing=True)
2195 repo[None].forget(list)
2197 repo[None].forget(list)
2196
2198
2197 if warn:
2199 if warn:
2198 for warning in warnings:
2200 for warning in warnings:
2199 ui.warn(warning)
2201 ui.warn(warning)
2200
2202
2201 return ret
2203 return ret
2202
2204
2203 def _updatecatformatter(fm, ctx, matcher, path, decode):
2205 def _updatecatformatter(fm, ctx, matcher, path, decode):
2204 """Hook for adding data to the formatter used by ``hg cat``.
2206 """Hook for adding data to the formatter used by ``hg cat``.
2205
2207
2206 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2208 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2207 this method first."""
2209 this method first."""
2208 data = ctx[path].data()
2210 data = ctx[path].data()
2209 if decode:
2211 if decode:
2210 data = ctx.repo().wwritedata(path, data)
2212 data = ctx.repo().wwritedata(path, data)
2211 fm.startitem()
2213 fm.startitem()
2212 fm.write('data', '%s', data)
2214 fm.write('data', '%s', data)
2213 fm.data(abspath=path, path=matcher.rel(path))
2215 fm.data(abspath=path, path=matcher.rel(path))
2214
2216
2215 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2217 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2216 err = 1
2218 err = 1
2217 opts = pycompat.byteskwargs(opts)
2219 opts = pycompat.byteskwargs(opts)
2218
2220
2219 def write(path):
2221 def write(path):
2220 filename = None
2222 filename = None
2221 if fntemplate:
2223 if fntemplate:
2222 filename = makefilename(ctx, fntemplate,
2224 filename = makefilename(ctx, fntemplate,
2223 pathname=os.path.join(prefix, path))
2225 pathname=os.path.join(prefix, path))
2224 # attempt to create the directory if it does not already exist
2226 # attempt to create the directory if it does not already exist
2225 try:
2227 try:
2226 os.makedirs(os.path.dirname(filename))
2228 os.makedirs(os.path.dirname(filename))
2227 except OSError:
2229 except OSError:
2228 pass
2230 pass
2229 with formatter.maybereopen(basefm, filename, opts) as fm:
2231 with formatter.maybereopen(basefm, filename, opts) as fm:
2230 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2232 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2231
2233
2232 # Automation often uses hg cat on single files, so special case it
2234 # Automation often uses hg cat on single files, so special case it
2233 # for performance to avoid the cost of parsing the manifest.
2235 # for performance to avoid the cost of parsing the manifest.
2234 if len(matcher.files()) == 1 and not matcher.anypats():
2236 if len(matcher.files()) == 1 and not matcher.anypats():
2235 file = matcher.files()[0]
2237 file = matcher.files()[0]
2236 mfl = repo.manifestlog
2238 mfl = repo.manifestlog
2237 mfnode = ctx.manifestnode()
2239 mfnode = ctx.manifestnode()
2238 try:
2240 try:
2239 if mfnode and mfl[mfnode].find(file)[0]:
2241 if mfnode and mfl[mfnode].find(file)[0]:
2240 scmutil.fileprefetchhooks(repo, ctx, [file])
2242 scmutil.fileprefetchhooks(repo, ctx, [file])
2241 write(file)
2243 write(file)
2242 return 0
2244 return 0
2243 except KeyError:
2245 except KeyError:
2244 pass
2246 pass
2245
2247
2246 files = [f for f in ctx.walk(matcher)]
2248 files = [f for f in ctx.walk(matcher)]
2247 scmutil.fileprefetchhooks(repo, ctx, files)
2249 scmutil.fileprefetchhooks(repo, ctx, files)
2248
2250
2249 for abs in files:
2251 for abs in files:
2250 write(abs)
2252 write(abs)
2251 err = 0
2253 err = 0
2252
2254
2253 for subpath in sorted(ctx.substate):
2255 for subpath in sorted(ctx.substate):
2254 sub = ctx.sub(subpath)
2256 sub = ctx.sub(subpath)
2255 try:
2257 try:
2256 submatch = matchmod.subdirmatcher(subpath, matcher)
2258 submatch = matchmod.subdirmatcher(subpath, matcher)
2257
2259
2258 if not sub.cat(submatch, basefm, fntemplate,
2260 if not sub.cat(submatch, basefm, fntemplate,
2259 os.path.join(prefix, sub._path),
2261 os.path.join(prefix, sub._path),
2260 **pycompat.strkwargs(opts)):
2262 **pycompat.strkwargs(opts)):
2261 err = 0
2263 err = 0
2262 except error.RepoLookupError:
2264 except error.RepoLookupError:
2263 ui.status(_("skipping missing subrepository: %s\n")
2265 ui.status(_("skipping missing subrepository: %s\n")
2264 % os.path.join(prefix, subpath))
2266 % os.path.join(prefix, subpath))
2265
2267
2266 return err
2268 return err
2267
2269
2268 def commit(ui, repo, commitfunc, pats, opts):
2270 def commit(ui, repo, commitfunc, pats, opts):
2269 '''commit the specified files or all outstanding changes'''
2271 '''commit the specified files or all outstanding changes'''
2270 date = opts.get('date')
2272 date = opts.get('date')
2271 if date:
2273 if date:
2272 opts['date'] = dateutil.parsedate(date)
2274 opts['date'] = dateutil.parsedate(date)
2273 message = logmessage(ui, opts)
2275 message = logmessage(ui, opts)
2274 matcher = scmutil.match(repo[None], pats, opts)
2276 matcher = scmutil.match(repo[None], pats, opts)
2275
2277
2276 dsguard = None
2278 dsguard = None
2277 # extract addremove carefully -- this function can be called from a command
2279 # extract addremove carefully -- this function can be called from a command
2278 # that doesn't support addremove
2280 # that doesn't support addremove
2279 if opts.get('addremove'):
2281 if opts.get('addremove'):
2280 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2282 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2281 with dsguard or util.nullcontextmanager():
2283 with dsguard or util.nullcontextmanager():
2282 if dsguard:
2284 if dsguard:
2283 if scmutil.addremove(repo, matcher, "", opts) != 0:
2285 if scmutil.addremove(repo, matcher, "", opts) != 0:
2284 raise error.Abort(
2286 raise error.Abort(
2285 _("failed to mark all new/missing files as added/removed"))
2287 _("failed to mark all new/missing files as added/removed"))
2286
2288
2287 return commitfunc(ui, repo, message, matcher, opts)
2289 return commitfunc(ui, repo, message, matcher, opts)
2288
2290
2289 def samefile(f, ctx1, ctx2):
2291 def samefile(f, ctx1, ctx2):
2290 if f in ctx1.manifest():
2292 if f in ctx1.manifest():
2291 a = ctx1.filectx(f)
2293 a = ctx1.filectx(f)
2292 if f in ctx2.manifest():
2294 if f in ctx2.manifest():
2293 b = ctx2.filectx(f)
2295 b = ctx2.filectx(f)
2294 return (not a.cmp(b)
2296 return (not a.cmp(b)
2295 and a.flags() == b.flags())
2297 and a.flags() == b.flags())
2296 else:
2298 else:
2297 return False
2299 return False
2298 else:
2300 else:
2299 return f not in ctx2.manifest()
2301 return f not in ctx2.manifest()
2300
2302
2301 def amend(ui, repo, old, extra, pats, opts):
2303 def amend(ui, repo, old, extra, pats, opts):
2302 # avoid cycle context -> subrepo -> cmdutil
2304 # avoid cycle context -> subrepo -> cmdutil
2303 from . import context
2305 from . import context
2304
2306
2305 # amend will reuse the existing user if not specified, but the obsolete
2307 # amend will reuse the existing user if not specified, but the obsolete
2306 # marker creation requires that the current user's name is specified.
2308 # marker creation requires that the current user's name is specified.
2307 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2309 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2308 ui.username() # raise exception if username not set
2310 ui.username() # raise exception if username not set
2309
2311
2310 ui.note(_('amending changeset %s\n') % old)
2312 ui.note(_('amending changeset %s\n') % old)
2311 base = old.p1()
2313 base = old.p1()
2312
2314
2313 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2315 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2314 # Participating changesets:
2316 # Participating changesets:
2315 #
2317 #
2316 # wctx o - workingctx that contains changes from working copy
2318 # wctx o - workingctx that contains changes from working copy
2317 # | to go into amending commit
2319 # | to go into amending commit
2318 # |
2320 # |
2319 # old o - changeset to amend
2321 # old o - changeset to amend
2320 # |
2322 # |
2321 # base o - first parent of the changeset to amend
2323 # base o - first parent of the changeset to amend
2322 wctx = repo[None]
2324 wctx = repo[None]
2323
2325
2324 # Copy to avoid mutating input
2326 # Copy to avoid mutating input
2325 extra = extra.copy()
2327 extra = extra.copy()
2326 # Update extra dict from amended commit (e.g. to preserve graft
2328 # Update extra dict from amended commit (e.g. to preserve graft
2327 # source)
2329 # source)
2328 extra.update(old.extra())
2330 extra.update(old.extra())
2329
2331
2330 # Also update it from the from the wctx
2332 # Also update it from the from the wctx
2331 extra.update(wctx.extra())
2333 extra.update(wctx.extra())
2332
2334
2333 user = opts.get('user') or old.user()
2335 user = opts.get('user') or old.user()
2334 date = opts.get('date') or old.date()
2336 date = opts.get('date') or old.date()
2335
2337
2336 # Parse the date to allow comparison between date and old.date()
2338 # Parse the date to allow comparison between date and old.date()
2337 date = dateutil.parsedate(date)
2339 date = dateutil.parsedate(date)
2338
2340
2339 if len(old.parents()) > 1:
2341 if len(old.parents()) > 1:
2340 # ctx.files() isn't reliable for merges, so fall back to the
2342 # ctx.files() isn't reliable for merges, so fall back to the
2341 # slower repo.status() method
2343 # slower repo.status() method
2342 files = set([fn for st in repo.status(base, old)[:3]
2344 files = set([fn for st in repo.status(base, old)[:3]
2343 for fn in st])
2345 for fn in st])
2344 else:
2346 else:
2345 files = set(old.files())
2347 files = set(old.files())
2346
2348
2347 # add/remove the files to the working copy if the "addremove" option
2349 # add/remove the files to the working copy if the "addremove" option
2348 # was specified.
2350 # was specified.
2349 matcher = scmutil.match(wctx, pats, opts)
2351 matcher = scmutil.match(wctx, pats, opts)
2350 if (opts.get('addremove')
2352 if (opts.get('addremove')
2351 and scmutil.addremove(repo, matcher, "", opts)):
2353 and scmutil.addremove(repo, matcher, "", opts)):
2352 raise error.Abort(
2354 raise error.Abort(
2353 _("failed to mark all new/missing files as added/removed"))
2355 _("failed to mark all new/missing files as added/removed"))
2354
2356
2355 # Check subrepos. This depends on in-place wctx._status update in
2357 # Check subrepos. This depends on in-place wctx._status update in
2356 # subrepo.precommit(). To minimize the risk of this hack, we do
2358 # subrepo.precommit(). To minimize the risk of this hack, we do
2357 # nothing if .hgsub does not exist.
2359 # nothing if .hgsub does not exist.
2358 if '.hgsub' in wctx or '.hgsub' in old:
2360 if '.hgsub' in wctx or '.hgsub' in old:
2359 subs, commitsubs, newsubstate = subrepoutil.precommit(
2361 subs, commitsubs, newsubstate = subrepoutil.precommit(
2360 ui, wctx, wctx._status, matcher)
2362 ui, wctx, wctx._status, matcher)
2361 # amend should abort if commitsubrepos is enabled
2363 # amend should abort if commitsubrepos is enabled
2362 assert not commitsubs
2364 assert not commitsubs
2363 if subs:
2365 if subs:
2364 subrepoutil.writestate(repo, newsubstate)
2366 subrepoutil.writestate(repo, newsubstate)
2365
2367
2366 ms = mergemod.mergestate.read(repo)
2368 ms = mergemod.mergestate.read(repo)
2367 mergeutil.checkunresolved(ms)
2369 mergeutil.checkunresolved(ms)
2368
2370
2369 filestoamend = set(f for f in wctx.files() if matcher(f))
2371 filestoamend = set(f for f in wctx.files() if matcher(f))
2370
2372
2371 changes = (len(filestoamend) > 0)
2373 changes = (len(filestoamend) > 0)
2372 if changes:
2374 if changes:
2373 # Recompute copies (avoid recording a -> b -> a)
2375 # Recompute copies (avoid recording a -> b -> a)
2374 copied = copies.pathcopies(base, wctx, matcher)
2376 copied = copies.pathcopies(base, wctx, matcher)
2375 if old.p2:
2377 if old.p2:
2376 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2378 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2377
2379
2378 # Prune files which were reverted by the updates: if old
2380 # Prune files which were reverted by the updates: if old
2379 # introduced file X and the file was renamed in the working
2381 # introduced file X and the file was renamed in the working
2380 # copy, then those two files are the same and
2382 # copy, then those two files are the same and
2381 # we can discard X from our list of files. Likewise if X
2383 # we can discard X from our list of files. Likewise if X
2382 # was removed, it's no longer relevant. If X is missing (aka
2384 # was removed, it's no longer relevant. If X is missing (aka
2383 # deleted), old X must be preserved.
2385 # deleted), old X must be preserved.
2384 files.update(filestoamend)
2386 files.update(filestoamend)
2385 files = [f for f in files if (not samefile(f, wctx, base)
2387 files = [f for f in files if (not samefile(f, wctx, base)
2386 or f in wctx.deleted())]
2388 or f in wctx.deleted())]
2387
2389
2388 def filectxfn(repo, ctx_, path):
2390 def filectxfn(repo, ctx_, path):
2389 try:
2391 try:
2390 # If the file being considered is not amongst the files
2392 # If the file being considered is not amongst the files
2391 # to be amended, we should return the file context from the
2393 # to be amended, we should return the file context from the
2392 # old changeset. This avoids issues when only some files in
2394 # old changeset. This avoids issues when only some files in
2393 # the working copy are being amended but there are also
2395 # the working copy are being amended but there are also
2394 # changes to other files from the old changeset.
2396 # changes to other files from the old changeset.
2395 if path not in filestoamend:
2397 if path not in filestoamend:
2396 return old.filectx(path)
2398 return old.filectx(path)
2397
2399
2398 # Return None for removed files.
2400 # Return None for removed files.
2399 if path in wctx.removed():
2401 if path in wctx.removed():
2400 return None
2402 return None
2401
2403
2402 fctx = wctx[path]
2404 fctx = wctx[path]
2403 flags = fctx.flags()
2405 flags = fctx.flags()
2404 mctx = context.memfilectx(repo, ctx_,
2406 mctx = context.memfilectx(repo, ctx_,
2405 fctx.path(), fctx.data(),
2407 fctx.path(), fctx.data(),
2406 islink='l' in flags,
2408 islink='l' in flags,
2407 isexec='x' in flags,
2409 isexec='x' in flags,
2408 copied=copied.get(path))
2410 copied=copied.get(path))
2409 return mctx
2411 return mctx
2410 except KeyError:
2412 except KeyError:
2411 return None
2413 return None
2412 else:
2414 else:
2413 ui.note(_('copying changeset %s to %s\n') % (old, base))
2415 ui.note(_('copying changeset %s to %s\n') % (old, base))
2414
2416
2415 # Use version of files as in the old cset
2417 # Use version of files as in the old cset
2416 def filectxfn(repo, ctx_, path):
2418 def filectxfn(repo, ctx_, path):
2417 try:
2419 try:
2418 return old.filectx(path)
2420 return old.filectx(path)
2419 except KeyError:
2421 except KeyError:
2420 return None
2422 return None
2421
2423
2422 # See if we got a message from -m or -l, if not, open the editor with
2424 # See if we got a message from -m or -l, if not, open the editor with
2423 # the message of the changeset to amend.
2425 # the message of the changeset to amend.
2424 message = logmessage(ui, opts)
2426 message = logmessage(ui, opts)
2425
2427
2426 editform = mergeeditform(old, 'commit.amend')
2428 editform = mergeeditform(old, 'commit.amend')
2427 editor = getcommiteditor(editform=editform,
2429 editor = getcommiteditor(editform=editform,
2428 **pycompat.strkwargs(opts))
2430 **pycompat.strkwargs(opts))
2429
2431
2430 if not message:
2432 if not message:
2431 editor = getcommiteditor(edit=True, editform=editform)
2433 editor = getcommiteditor(edit=True, editform=editform)
2432 message = old.description()
2434 message = old.description()
2433
2435
2434 pureextra = extra.copy()
2436 pureextra = extra.copy()
2435 extra['amend_source'] = old.hex()
2437 extra['amend_source'] = old.hex()
2436
2438
2437 new = context.memctx(repo,
2439 new = context.memctx(repo,
2438 parents=[base.node(), old.p2().node()],
2440 parents=[base.node(), old.p2().node()],
2439 text=message,
2441 text=message,
2440 files=files,
2442 files=files,
2441 filectxfn=filectxfn,
2443 filectxfn=filectxfn,
2442 user=user,
2444 user=user,
2443 date=date,
2445 date=date,
2444 extra=extra,
2446 extra=extra,
2445 editor=editor)
2447 editor=editor)
2446
2448
2447 newdesc = changelog.stripdesc(new.description())
2449 newdesc = changelog.stripdesc(new.description())
2448 if ((not changes)
2450 if ((not changes)
2449 and newdesc == old.description()
2451 and newdesc == old.description()
2450 and user == old.user()
2452 and user == old.user()
2451 and date == old.date()
2453 and date == old.date()
2452 and pureextra == old.extra()):
2454 and pureextra == old.extra()):
2453 # nothing changed. continuing here would create a new node
2455 # nothing changed. continuing here would create a new node
2454 # anyway because of the amend_source noise.
2456 # anyway because of the amend_source noise.
2455 #
2457 #
2456 # This not what we expect from amend.
2458 # This not what we expect from amend.
2457 return old.node()
2459 return old.node()
2458
2460
2459 if opts.get('secret'):
2461 if opts.get('secret'):
2460 commitphase = 'secret'
2462 commitphase = 'secret'
2461 else:
2463 else:
2462 commitphase = old.phase()
2464 commitphase = old.phase()
2463 overrides = {('phases', 'new-commit'): commitphase}
2465 overrides = {('phases', 'new-commit'): commitphase}
2464 with ui.configoverride(overrides, 'amend'):
2466 with ui.configoverride(overrides, 'amend'):
2465 newid = repo.commitctx(new)
2467 newid = repo.commitctx(new)
2466
2468
2467 # Reroute the working copy parent to the new changeset
2469 # Reroute the working copy parent to the new changeset
2468 repo.setparents(newid, nullid)
2470 repo.setparents(newid, nullid)
2469 mapping = {old.node(): (newid,)}
2471 mapping = {old.node(): (newid,)}
2470 obsmetadata = None
2472 obsmetadata = None
2471 if opts.get('note'):
2473 if opts.get('note'):
2472 obsmetadata = {'note': opts['note']}
2474 obsmetadata = {'note': opts['note']}
2473 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
2475 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
2474
2476
2475 # Fixing the dirstate because localrepo.commitctx does not update
2477 # Fixing the dirstate because localrepo.commitctx does not update
2476 # it. This is rather convenient because we did not need to update
2478 # it. This is rather convenient because we did not need to update
2477 # the dirstate for all the files in the new commit which commitctx
2479 # the dirstate for all the files in the new commit which commitctx
2478 # could have done if it updated the dirstate. Now, we can
2480 # could have done if it updated the dirstate. Now, we can
2479 # selectively update the dirstate only for the amended files.
2481 # selectively update the dirstate only for the amended files.
2480 dirstate = repo.dirstate
2482 dirstate = repo.dirstate
2481
2483
2482 # Update the state of the files which were added and
2484 # Update the state of the files which were added and
2483 # and modified in the amend to "normal" in the dirstate.
2485 # and modified in the amend to "normal" in the dirstate.
2484 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2486 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2485 for f in normalfiles:
2487 for f in normalfiles:
2486 dirstate.normal(f)
2488 dirstate.normal(f)
2487
2489
2488 # Update the state of files which were removed in the amend
2490 # Update the state of files which were removed in the amend
2489 # to "removed" in the dirstate.
2491 # to "removed" in the dirstate.
2490 removedfiles = set(wctx.removed()) & filestoamend
2492 removedfiles = set(wctx.removed()) & filestoamend
2491 for f in removedfiles:
2493 for f in removedfiles:
2492 dirstate.drop(f)
2494 dirstate.drop(f)
2493
2495
2494 return newid
2496 return newid
2495
2497
2496 def commiteditor(repo, ctx, subs, editform=''):
2498 def commiteditor(repo, ctx, subs, editform=''):
2497 if ctx.description():
2499 if ctx.description():
2498 return ctx.description()
2500 return ctx.description()
2499 return commitforceeditor(repo, ctx, subs, editform=editform,
2501 return commitforceeditor(repo, ctx, subs, editform=editform,
2500 unchangedmessagedetection=True)
2502 unchangedmessagedetection=True)
2501
2503
2502 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2504 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2503 editform='', unchangedmessagedetection=False):
2505 editform='', unchangedmessagedetection=False):
2504 if not extramsg:
2506 if not extramsg:
2505 extramsg = _("Leave message empty to abort commit.")
2507 extramsg = _("Leave message empty to abort commit.")
2506
2508
2507 forms = [e for e in editform.split('.') if e]
2509 forms = [e for e in editform.split('.') if e]
2508 forms.insert(0, 'changeset')
2510 forms.insert(0, 'changeset')
2509 templatetext = None
2511 templatetext = None
2510 while forms:
2512 while forms:
2511 ref = '.'.join(forms)
2513 ref = '.'.join(forms)
2512 if repo.ui.config('committemplate', ref):
2514 if repo.ui.config('committemplate', ref):
2513 templatetext = committext = buildcommittemplate(
2515 templatetext = committext = buildcommittemplate(
2514 repo, ctx, subs, extramsg, ref)
2516 repo, ctx, subs, extramsg, ref)
2515 break
2517 break
2516 forms.pop()
2518 forms.pop()
2517 else:
2519 else:
2518 committext = buildcommittext(repo, ctx, subs, extramsg)
2520 committext = buildcommittext(repo, ctx, subs, extramsg)
2519
2521
2520 # run editor in the repository root
2522 # run editor in the repository root
2521 olddir = pycompat.getcwd()
2523 olddir = pycompat.getcwd()
2522 os.chdir(repo.root)
2524 os.chdir(repo.root)
2523
2525
2524 # make in-memory changes visible to external process
2526 # make in-memory changes visible to external process
2525 tr = repo.currenttransaction()
2527 tr = repo.currenttransaction()
2526 repo.dirstate.write(tr)
2528 repo.dirstate.write(tr)
2527 pending = tr and tr.writepending() and repo.root
2529 pending = tr and tr.writepending() and repo.root
2528
2530
2529 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2531 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2530 editform=editform, pending=pending,
2532 editform=editform, pending=pending,
2531 repopath=repo.path, action='commit')
2533 repopath=repo.path, action='commit')
2532 text = editortext
2534 text = editortext
2533
2535
2534 # strip away anything below this special string (used for editors that want
2536 # strip away anything below this special string (used for editors that want
2535 # to display the diff)
2537 # to display the diff)
2536 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2538 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2537 if stripbelow:
2539 if stripbelow:
2538 text = text[:stripbelow.start()]
2540 text = text[:stripbelow.start()]
2539
2541
2540 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2542 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2541 os.chdir(olddir)
2543 os.chdir(olddir)
2542
2544
2543 if finishdesc:
2545 if finishdesc:
2544 text = finishdesc(text)
2546 text = finishdesc(text)
2545 if not text.strip():
2547 if not text.strip():
2546 raise error.Abort(_("empty commit message"))
2548 raise error.Abort(_("empty commit message"))
2547 if unchangedmessagedetection and editortext == templatetext:
2549 if unchangedmessagedetection and editortext == templatetext:
2548 raise error.Abort(_("commit message unchanged"))
2550 raise error.Abort(_("commit message unchanged"))
2549
2551
2550 return text
2552 return text
2551
2553
2552 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2554 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2553 ui = repo.ui
2555 ui = repo.ui
2554 spec = formatter.templatespec(ref, None, None)
2556 spec = formatter.templatespec(ref, None, None)
2555 t = logcmdutil.changesettemplater(ui, repo, spec)
2557 t = logcmdutil.changesettemplater(ui, repo, spec)
2556 t.t.cache.update((k, templater.unquotestring(v))
2558 t.t.cache.update((k, templater.unquotestring(v))
2557 for k, v in repo.ui.configitems('committemplate'))
2559 for k, v in repo.ui.configitems('committemplate'))
2558
2560
2559 if not extramsg:
2561 if not extramsg:
2560 extramsg = '' # ensure that extramsg is string
2562 extramsg = '' # ensure that extramsg is string
2561
2563
2562 ui.pushbuffer()
2564 ui.pushbuffer()
2563 t.show(ctx, extramsg=extramsg)
2565 t.show(ctx, extramsg=extramsg)
2564 return ui.popbuffer()
2566 return ui.popbuffer()
2565
2567
2566 def hgprefix(msg):
2568 def hgprefix(msg):
2567 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2569 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2568
2570
2569 def buildcommittext(repo, ctx, subs, extramsg):
2571 def buildcommittext(repo, ctx, subs, extramsg):
2570 edittext = []
2572 edittext = []
2571 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2573 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2572 if ctx.description():
2574 if ctx.description():
2573 edittext.append(ctx.description())
2575 edittext.append(ctx.description())
2574 edittext.append("")
2576 edittext.append("")
2575 edittext.append("") # Empty line between message and comments.
2577 edittext.append("") # Empty line between message and comments.
2576 edittext.append(hgprefix(_("Enter commit message."
2578 edittext.append(hgprefix(_("Enter commit message."
2577 " Lines beginning with 'HG:' are removed.")))
2579 " Lines beginning with 'HG:' are removed.")))
2578 edittext.append(hgprefix(extramsg))
2580 edittext.append(hgprefix(extramsg))
2579 edittext.append("HG: --")
2581 edittext.append("HG: --")
2580 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2582 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2581 if ctx.p2():
2583 if ctx.p2():
2582 edittext.append(hgprefix(_("branch merge")))
2584 edittext.append(hgprefix(_("branch merge")))
2583 if ctx.branch():
2585 if ctx.branch():
2584 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2586 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2585 if bookmarks.isactivewdirparent(repo):
2587 if bookmarks.isactivewdirparent(repo):
2586 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2588 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2587 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2589 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2588 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2590 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2589 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2591 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2590 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2592 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2591 if not added and not modified and not removed:
2593 if not added and not modified and not removed:
2592 edittext.append(hgprefix(_("no files changed")))
2594 edittext.append(hgprefix(_("no files changed")))
2593 edittext.append("")
2595 edittext.append("")
2594
2596
2595 return "\n".join(edittext)
2597 return "\n".join(edittext)
2596
2598
2597 def commitstatus(repo, node, branch, bheads=None, opts=None):
2599 def commitstatus(repo, node, branch, bheads=None, opts=None):
2598 if opts is None:
2600 if opts is None:
2599 opts = {}
2601 opts = {}
2600 ctx = repo[node]
2602 ctx = repo[node]
2601 parents = ctx.parents()
2603 parents = ctx.parents()
2602
2604
2603 if (not opts.get('amend') and bheads and node not in bheads and not
2605 if (not opts.get('amend') and bheads and node not in bheads and not
2604 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2606 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2605 repo.ui.status(_('created new head\n'))
2607 repo.ui.status(_('created new head\n'))
2606 # The message is not printed for initial roots. For the other
2608 # The message is not printed for initial roots. For the other
2607 # changesets, it is printed in the following situations:
2609 # changesets, it is printed in the following situations:
2608 #
2610 #
2609 # Par column: for the 2 parents with ...
2611 # Par column: for the 2 parents with ...
2610 # N: null or no parent
2612 # N: null or no parent
2611 # B: parent is on another named branch
2613 # B: parent is on another named branch
2612 # C: parent is a regular non head changeset
2614 # C: parent is a regular non head changeset
2613 # H: parent was a branch head of the current branch
2615 # H: parent was a branch head of the current branch
2614 # Msg column: whether we print "created new head" message
2616 # Msg column: whether we print "created new head" message
2615 # In the following, it is assumed that there already exists some
2617 # In the following, it is assumed that there already exists some
2616 # initial branch heads of the current branch, otherwise nothing is
2618 # initial branch heads of the current branch, otherwise nothing is
2617 # printed anyway.
2619 # printed anyway.
2618 #
2620 #
2619 # Par Msg Comment
2621 # Par Msg Comment
2620 # N N y additional topo root
2622 # N N y additional topo root
2621 #
2623 #
2622 # B N y additional branch root
2624 # B N y additional branch root
2623 # C N y additional topo head
2625 # C N y additional topo head
2624 # H N n usual case
2626 # H N n usual case
2625 #
2627 #
2626 # B B y weird additional branch root
2628 # B B y weird additional branch root
2627 # C B y branch merge
2629 # C B y branch merge
2628 # H B n merge with named branch
2630 # H B n merge with named branch
2629 #
2631 #
2630 # C C y additional head from merge
2632 # C C y additional head from merge
2631 # C H n merge with a head
2633 # C H n merge with a head
2632 #
2634 #
2633 # H H n head merge: head count decreases
2635 # H H n head merge: head count decreases
2634
2636
2635 if not opts.get('close_branch'):
2637 if not opts.get('close_branch'):
2636 for r in parents:
2638 for r in parents:
2637 if r.closesbranch() and r.branch() == branch:
2639 if r.closesbranch() and r.branch() == branch:
2638 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2640 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2639
2641
2640 if repo.ui.debugflag:
2642 if repo.ui.debugflag:
2641 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2643 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2642 elif repo.ui.verbose:
2644 elif repo.ui.verbose:
2643 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2645 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2644
2646
2645 def postcommitstatus(repo, pats, opts):
2647 def postcommitstatus(repo, pats, opts):
2646 return repo.status(match=scmutil.match(repo[None], pats, opts))
2648 return repo.status(match=scmutil.match(repo[None], pats, opts))
2647
2649
2648 def revert(ui, repo, ctx, parents, *pats, **opts):
2650 def revert(ui, repo, ctx, parents, *pats, **opts):
2649 opts = pycompat.byteskwargs(opts)
2651 opts = pycompat.byteskwargs(opts)
2650 parent, p2 = parents
2652 parent, p2 = parents
2651 node = ctx.node()
2653 node = ctx.node()
2652
2654
2653 mf = ctx.manifest()
2655 mf = ctx.manifest()
2654 if node == p2:
2656 if node == p2:
2655 parent = p2
2657 parent = p2
2656
2658
2657 # need all matching names in dirstate and manifest of target rev,
2659 # need all matching names in dirstate and manifest of target rev,
2658 # so have to walk both. do not print errors if files exist in one
2660 # so have to walk both. do not print errors if files exist in one
2659 # but not other. in both cases, filesets should be evaluated against
2661 # but not other. in both cases, filesets should be evaluated against
2660 # workingctx to get consistent result (issue4497). this means 'set:**'
2662 # workingctx to get consistent result (issue4497). this means 'set:**'
2661 # cannot be used to select missing files from target rev.
2663 # cannot be used to select missing files from target rev.
2662
2664
2663 # `names` is a mapping for all elements in working copy and target revision
2665 # `names` is a mapping for all elements in working copy and target revision
2664 # The mapping is in the form:
2666 # The mapping is in the form:
2665 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2667 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2666 names = {}
2668 names = {}
2667
2669
2668 with repo.wlock():
2670 with repo.wlock():
2669 ## filling of the `names` mapping
2671 ## filling of the `names` mapping
2670 # walk dirstate to fill `names`
2672 # walk dirstate to fill `names`
2671
2673
2672 interactive = opts.get('interactive', False)
2674 interactive = opts.get('interactive', False)
2673 wctx = repo[None]
2675 wctx = repo[None]
2674 m = scmutil.match(wctx, pats, opts)
2676 m = scmutil.match(wctx, pats, opts)
2675
2677
2676 # we'll need this later
2678 # we'll need this later
2677 targetsubs = sorted(s for s in wctx.substate if m(s))
2679 targetsubs = sorted(s for s in wctx.substate if m(s))
2678
2680
2679 if not m.always():
2681 if not m.always():
2680 matcher = matchmod.badmatch(m, lambda x, y: False)
2682 matcher = matchmod.badmatch(m, lambda x, y: False)
2681 for abs in wctx.walk(matcher):
2683 for abs in wctx.walk(matcher):
2682 names[abs] = m.rel(abs), m.exact(abs)
2684 names[abs] = m.rel(abs), m.exact(abs)
2683
2685
2684 # walk target manifest to fill `names`
2686 # walk target manifest to fill `names`
2685
2687
2686 def badfn(path, msg):
2688 def badfn(path, msg):
2687 if path in names:
2689 if path in names:
2688 return
2690 return
2689 if path in ctx.substate:
2691 if path in ctx.substate:
2690 return
2692 return
2691 path_ = path + '/'
2693 path_ = path + '/'
2692 for f in names:
2694 for f in names:
2693 if f.startswith(path_):
2695 if f.startswith(path_):
2694 return
2696 return
2695 ui.warn("%s: %s\n" % (m.rel(path), msg))
2697 ui.warn("%s: %s\n" % (m.rel(path), msg))
2696
2698
2697 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2699 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2698 if abs not in names:
2700 if abs not in names:
2699 names[abs] = m.rel(abs), m.exact(abs)
2701 names[abs] = m.rel(abs), m.exact(abs)
2700
2702
2701 # Find status of all file in `names`.
2703 # Find status of all file in `names`.
2702 m = scmutil.matchfiles(repo, names)
2704 m = scmutil.matchfiles(repo, names)
2703
2705
2704 changes = repo.status(node1=node, match=m,
2706 changes = repo.status(node1=node, match=m,
2705 unknown=True, ignored=True, clean=True)
2707 unknown=True, ignored=True, clean=True)
2706 else:
2708 else:
2707 changes = repo.status(node1=node, match=m)
2709 changes = repo.status(node1=node, match=m)
2708 for kind in changes:
2710 for kind in changes:
2709 for abs in kind:
2711 for abs in kind:
2710 names[abs] = m.rel(abs), m.exact(abs)
2712 names[abs] = m.rel(abs), m.exact(abs)
2711
2713
2712 m = scmutil.matchfiles(repo, names)
2714 m = scmutil.matchfiles(repo, names)
2713
2715
2714 modified = set(changes.modified)
2716 modified = set(changes.modified)
2715 added = set(changes.added)
2717 added = set(changes.added)
2716 removed = set(changes.removed)
2718 removed = set(changes.removed)
2717 _deleted = set(changes.deleted)
2719 _deleted = set(changes.deleted)
2718 unknown = set(changes.unknown)
2720 unknown = set(changes.unknown)
2719 unknown.update(changes.ignored)
2721 unknown.update(changes.ignored)
2720 clean = set(changes.clean)
2722 clean = set(changes.clean)
2721 modadded = set()
2723 modadded = set()
2722
2724
2723 # We need to account for the state of the file in the dirstate,
2725 # We need to account for the state of the file in the dirstate,
2724 # even when we revert against something else than parent. This will
2726 # even when we revert against something else than parent. This will
2725 # slightly alter the behavior of revert (doing back up or not, delete
2727 # slightly alter the behavior of revert (doing back up or not, delete
2726 # or just forget etc).
2728 # or just forget etc).
2727 if parent == node:
2729 if parent == node:
2728 dsmodified = modified
2730 dsmodified = modified
2729 dsadded = added
2731 dsadded = added
2730 dsremoved = removed
2732 dsremoved = removed
2731 # store all local modifications, useful later for rename detection
2733 # store all local modifications, useful later for rename detection
2732 localchanges = dsmodified | dsadded
2734 localchanges = dsmodified | dsadded
2733 modified, added, removed = set(), set(), set()
2735 modified, added, removed = set(), set(), set()
2734 else:
2736 else:
2735 changes = repo.status(node1=parent, match=m)
2737 changes = repo.status(node1=parent, match=m)
2736 dsmodified = set(changes.modified)
2738 dsmodified = set(changes.modified)
2737 dsadded = set(changes.added)
2739 dsadded = set(changes.added)
2738 dsremoved = set(changes.removed)
2740 dsremoved = set(changes.removed)
2739 # store all local modifications, useful later for rename detection
2741 # store all local modifications, useful later for rename detection
2740 localchanges = dsmodified | dsadded
2742 localchanges = dsmodified | dsadded
2741
2743
2742 # only take into account for removes between wc and target
2744 # only take into account for removes between wc and target
2743 clean |= dsremoved - removed
2745 clean |= dsremoved - removed
2744 dsremoved &= removed
2746 dsremoved &= removed
2745 # distinct between dirstate remove and other
2747 # distinct between dirstate remove and other
2746 removed -= dsremoved
2748 removed -= dsremoved
2747
2749
2748 modadded = added & dsmodified
2750 modadded = added & dsmodified
2749 added -= modadded
2751 added -= modadded
2750
2752
2751 # tell newly modified apart.
2753 # tell newly modified apart.
2752 dsmodified &= modified
2754 dsmodified &= modified
2753 dsmodified |= modified & dsadded # dirstate added may need backup
2755 dsmodified |= modified & dsadded # dirstate added may need backup
2754 modified -= dsmodified
2756 modified -= dsmodified
2755
2757
2756 # We need to wait for some post-processing to update this set
2758 # We need to wait for some post-processing to update this set
2757 # before making the distinction. The dirstate will be used for
2759 # before making the distinction. The dirstate will be used for
2758 # that purpose.
2760 # that purpose.
2759 dsadded = added
2761 dsadded = added
2760
2762
2761 # in case of merge, files that are actually added can be reported as
2763 # in case of merge, files that are actually added can be reported as
2762 # modified, we need to post process the result
2764 # modified, we need to post process the result
2763 if p2 != nullid:
2765 if p2 != nullid:
2764 mergeadd = set(dsmodified)
2766 mergeadd = set(dsmodified)
2765 for path in dsmodified:
2767 for path in dsmodified:
2766 if path in mf:
2768 if path in mf:
2767 mergeadd.remove(path)
2769 mergeadd.remove(path)
2768 dsadded |= mergeadd
2770 dsadded |= mergeadd
2769 dsmodified -= mergeadd
2771 dsmodified -= mergeadd
2770
2772
2771 # if f is a rename, update `names` to also revert the source
2773 # if f is a rename, update `names` to also revert the source
2772 cwd = repo.getcwd()
2774 cwd = repo.getcwd()
2773 for f in localchanges:
2775 for f in localchanges:
2774 src = repo.dirstate.copied(f)
2776 src = repo.dirstate.copied(f)
2775 # XXX should we check for rename down to target node?
2777 # XXX should we check for rename down to target node?
2776 if src and src not in names and repo.dirstate[src] == 'r':
2778 if src and src not in names and repo.dirstate[src] == 'r':
2777 dsremoved.add(src)
2779 dsremoved.add(src)
2778 names[src] = (repo.pathto(src, cwd), True)
2780 names[src] = (repo.pathto(src, cwd), True)
2779
2781
2780 # determine the exact nature of the deleted changesets
2782 # determine the exact nature of the deleted changesets
2781 deladded = set(_deleted)
2783 deladded = set(_deleted)
2782 for path in _deleted:
2784 for path in _deleted:
2783 if path in mf:
2785 if path in mf:
2784 deladded.remove(path)
2786 deladded.remove(path)
2785 deleted = _deleted - deladded
2787 deleted = _deleted - deladded
2786
2788
2787 # distinguish between file to forget and the other
2789 # distinguish between file to forget and the other
2788 added = set()
2790 added = set()
2789 for abs in dsadded:
2791 for abs in dsadded:
2790 if repo.dirstate[abs] != 'a':
2792 if repo.dirstate[abs] != 'a':
2791 added.add(abs)
2793 added.add(abs)
2792 dsadded -= added
2794 dsadded -= added
2793
2795
2794 for abs in deladded:
2796 for abs in deladded:
2795 if repo.dirstate[abs] == 'a':
2797 if repo.dirstate[abs] == 'a':
2796 dsadded.add(abs)
2798 dsadded.add(abs)
2797 deladded -= dsadded
2799 deladded -= dsadded
2798
2800
2799 # For files marked as removed, we check if an unknown file is present at
2801 # For files marked as removed, we check if an unknown file is present at
2800 # the same path. If a such file exists it may need to be backed up.
2802 # the same path. If a such file exists it may need to be backed up.
2801 # Making the distinction at this stage helps have simpler backup
2803 # Making the distinction at this stage helps have simpler backup
2802 # logic.
2804 # logic.
2803 removunk = set()
2805 removunk = set()
2804 for abs in removed:
2806 for abs in removed:
2805 target = repo.wjoin(abs)
2807 target = repo.wjoin(abs)
2806 if os.path.lexists(target):
2808 if os.path.lexists(target):
2807 removunk.add(abs)
2809 removunk.add(abs)
2808 removed -= removunk
2810 removed -= removunk
2809
2811
2810 dsremovunk = set()
2812 dsremovunk = set()
2811 for abs in dsremoved:
2813 for abs in dsremoved:
2812 target = repo.wjoin(abs)
2814 target = repo.wjoin(abs)
2813 if os.path.lexists(target):
2815 if os.path.lexists(target):
2814 dsremovunk.add(abs)
2816 dsremovunk.add(abs)
2815 dsremoved -= dsremovunk
2817 dsremoved -= dsremovunk
2816
2818
2817 # action to be actually performed by revert
2819 # action to be actually performed by revert
2818 # (<list of file>, message>) tuple
2820 # (<list of file>, message>) tuple
2819 actions = {'revert': ([], _('reverting %s\n')),
2821 actions = {'revert': ([], _('reverting %s\n')),
2820 'add': ([], _('adding %s\n')),
2822 'add': ([], _('adding %s\n')),
2821 'remove': ([], _('removing %s\n')),
2823 'remove': ([], _('removing %s\n')),
2822 'drop': ([], _('removing %s\n')),
2824 'drop': ([], _('removing %s\n')),
2823 'forget': ([], _('forgetting %s\n')),
2825 'forget': ([], _('forgetting %s\n')),
2824 'undelete': ([], _('undeleting %s\n')),
2826 'undelete': ([], _('undeleting %s\n')),
2825 'noop': (None, _('no changes needed to %s\n')),
2827 'noop': (None, _('no changes needed to %s\n')),
2826 'unknown': (None, _('file not managed: %s\n')),
2828 'unknown': (None, _('file not managed: %s\n')),
2827 }
2829 }
2828
2830
2829 # "constant" that convey the backup strategy.
2831 # "constant" that convey the backup strategy.
2830 # All set to `discard` if `no-backup` is set do avoid checking
2832 # All set to `discard` if `no-backup` is set do avoid checking
2831 # no_backup lower in the code.
2833 # no_backup lower in the code.
2832 # These values are ordered for comparison purposes
2834 # These values are ordered for comparison purposes
2833 backupinteractive = 3 # do backup if interactively modified
2835 backupinteractive = 3 # do backup if interactively modified
2834 backup = 2 # unconditionally do backup
2836 backup = 2 # unconditionally do backup
2835 check = 1 # check if the existing file differs from target
2837 check = 1 # check if the existing file differs from target
2836 discard = 0 # never do backup
2838 discard = 0 # never do backup
2837 if opts.get('no_backup'):
2839 if opts.get('no_backup'):
2838 backupinteractive = backup = check = discard
2840 backupinteractive = backup = check = discard
2839 if interactive:
2841 if interactive:
2840 dsmodifiedbackup = backupinteractive
2842 dsmodifiedbackup = backupinteractive
2841 else:
2843 else:
2842 dsmodifiedbackup = backup
2844 dsmodifiedbackup = backup
2843 tobackup = set()
2845 tobackup = set()
2844
2846
2845 backupanddel = actions['remove']
2847 backupanddel = actions['remove']
2846 if not opts.get('no_backup'):
2848 if not opts.get('no_backup'):
2847 backupanddel = actions['drop']
2849 backupanddel = actions['drop']
2848
2850
2849 disptable = (
2851 disptable = (
2850 # dispatch table:
2852 # dispatch table:
2851 # file state
2853 # file state
2852 # action
2854 # action
2853 # make backup
2855 # make backup
2854
2856
2855 ## Sets that results that will change file on disk
2857 ## Sets that results that will change file on disk
2856 # Modified compared to target, no local change
2858 # Modified compared to target, no local change
2857 (modified, actions['revert'], discard),
2859 (modified, actions['revert'], discard),
2858 # Modified compared to target, but local file is deleted
2860 # Modified compared to target, but local file is deleted
2859 (deleted, actions['revert'], discard),
2861 (deleted, actions['revert'], discard),
2860 # Modified compared to target, local change
2862 # Modified compared to target, local change
2861 (dsmodified, actions['revert'], dsmodifiedbackup),
2863 (dsmodified, actions['revert'], dsmodifiedbackup),
2862 # Added since target
2864 # Added since target
2863 (added, actions['remove'], discard),
2865 (added, actions['remove'], discard),
2864 # Added in working directory
2866 # Added in working directory
2865 (dsadded, actions['forget'], discard),
2867 (dsadded, actions['forget'], discard),
2866 # Added since target, have local modification
2868 # Added since target, have local modification
2867 (modadded, backupanddel, backup),
2869 (modadded, backupanddel, backup),
2868 # Added since target but file is missing in working directory
2870 # Added since target but file is missing in working directory
2869 (deladded, actions['drop'], discard),
2871 (deladded, actions['drop'], discard),
2870 # Removed since target, before working copy parent
2872 # Removed since target, before working copy parent
2871 (removed, actions['add'], discard),
2873 (removed, actions['add'], discard),
2872 # Same as `removed` but an unknown file exists at the same path
2874 # Same as `removed` but an unknown file exists at the same path
2873 (removunk, actions['add'], check),
2875 (removunk, actions['add'], check),
2874 # Removed since targe, marked as such in working copy parent
2876 # Removed since targe, marked as such in working copy parent
2875 (dsremoved, actions['undelete'], discard),
2877 (dsremoved, actions['undelete'], discard),
2876 # Same as `dsremoved` but an unknown file exists at the same path
2878 # Same as `dsremoved` but an unknown file exists at the same path
2877 (dsremovunk, actions['undelete'], check),
2879 (dsremovunk, actions['undelete'], check),
2878 ## the following sets does not result in any file changes
2880 ## the following sets does not result in any file changes
2879 # File with no modification
2881 # File with no modification
2880 (clean, actions['noop'], discard),
2882 (clean, actions['noop'], discard),
2881 # Existing file, not tracked anywhere
2883 # Existing file, not tracked anywhere
2882 (unknown, actions['unknown'], discard),
2884 (unknown, actions['unknown'], discard),
2883 )
2885 )
2884
2886
2885 for abs, (rel, exact) in sorted(names.items()):
2887 for abs, (rel, exact) in sorted(names.items()):
2886 # target file to be touch on disk (relative to cwd)
2888 # target file to be touch on disk (relative to cwd)
2887 target = repo.wjoin(abs)
2889 target = repo.wjoin(abs)
2888 # search the entry in the dispatch table.
2890 # search the entry in the dispatch table.
2889 # if the file is in any of these sets, it was touched in the working
2891 # if the file is in any of these sets, it was touched in the working
2890 # directory parent and we are sure it needs to be reverted.
2892 # directory parent and we are sure it needs to be reverted.
2891 for table, (xlist, msg), dobackup in disptable:
2893 for table, (xlist, msg), dobackup in disptable:
2892 if abs not in table:
2894 if abs not in table:
2893 continue
2895 continue
2894 if xlist is not None:
2896 if xlist is not None:
2895 xlist.append(abs)
2897 xlist.append(abs)
2896 if dobackup:
2898 if dobackup:
2897 # If in interactive mode, don't automatically create
2899 # If in interactive mode, don't automatically create
2898 # .orig files (issue4793)
2900 # .orig files (issue4793)
2899 if dobackup == backupinteractive:
2901 if dobackup == backupinteractive:
2900 tobackup.add(abs)
2902 tobackup.add(abs)
2901 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2903 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2902 bakname = scmutil.origpath(ui, repo, rel)
2904 bakname = scmutil.origpath(ui, repo, rel)
2903 ui.note(_('saving current version of %s as %s\n') %
2905 ui.note(_('saving current version of %s as %s\n') %
2904 (rel, bakname))
2906 (rel, bakname))
2905 if not opts.get('dry_run'):
2907 if not opts.get('dry_run'):
2906 if interactive:
2908 if interactive:
2907 util.copyfile(target, bakname)
2909 util.copyfile(target, bakname)
2908 else:
2910 else:
2909 util.rename(target, bakname)
2911 util.rename(target, bakname)
2910 if ui.verbose or not exact:
2912 if ui.verbose or not exact:
2911 if not isinstance(msg, bytes):
2913 if not isinstance(msg, bytes):
2912 msg = msg(abs)
2914 msg = msg(abs)
2913 ui.status(msg % rel)
2915 ui.status(msg % rel)
2914 elif exact:
2916 elif exact:
2915 ui.warn(msg % rel)
2917 ui.warn(msg % rel)
2916 break
2918 break
2917
2919
2918 if not opts.get('dry_run'):
2920 if not opts.get('dry_run'):
2919 needdata = ('revert', 'add', 'undelete')
2921 needdata = ('revert', 'add', 'undelete')
2920 if _revertprefetch is not _revertprefetchstub:
2922 if _revertprefetch is not _revertprefetchstub:
2921 ui.deprecwarn("'cmdutil._revertprefetch' is deprecated, "
2923 ui.deprecwarn("'cmdutil._revertprefetch' is deprecated, "
2922 "add a callback to 'scmutil.fileprefetchhooks'",
2924 "add a callback to 'scmutil.fileprefetchhooks'",
2923 '4.6', stacklevel=1)
2925 '4.6', stacklevel=1)
2924 _revertprefetch(repo, ctx,
2926 _revertprefetch(repo, ctx,
2925 *[actions[name][0] for name in needdata])
2927 *[actions[name][0] for name in needdata])
2926 oplist = [actions[name][0] for name in needdata]
2928 oplist = [actions[name][0] for name in needdata]
2927 prefetch = scmutil.fileprefetchhooks
2929 prefetch = scmutil.fileprefetchhooks
2928 prefetch(repo, ctx, [f for sublist in oplist for f in sublist])
2930 prefetch(repo, ctx, [f for sublist in oplist for f in sublist])
2929 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
2931 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
2930
2932
2931 if targetsubs:
2933 if targetsubs:
2932 # Revert the subrepos on the revert list
2934 # Revert the subrepos on the revert list
2933 for sub in targetsubs:
2935 for sub in targetsubs:
2934 try:
2936 try:
2935 wctx.sub(sub).revert(ctx.substate[sub], *pats,
2937 wctx.sub(sub).revert(ctx.substate[sub], *pats,
2936 **pycompat.strkwargs(opts))
2938 **pycompat.strkwargs(opts))
2937 except KeyError:
2939 except KeyError:
2938 raise error.Abort("subrepository '%s' does not exist in %s!"
2940 raise error.Abort("subrepository '%s' does not exist in %s!"
2939 % (sub, short(ctx.node())))
2941 % (sub, short(ctx.node())))
2940
2942
2941 def _revertprefetchstub(repo, ctx, *files):
2943 def _revertprefetchstub(repo, ctx, *files):
2942 """Stub method for detecting extension wrapping of _revertprefetch(), to
2944 """Stub method for detecting extension wrapping of _revertprefetch(), to
2943 issue a deprecation warning."""
2945 issue a deprecation warning."""
2944
2946
2945 _revertprefetch = _revertprefetchstub
2947 _revertprefetch = _revertprefetchstub
2946
2948
2947 def _performrevert(repo, parents, ctx, actions, interactive=False,
2949 def _performrevert(repo, parents, ctx, actions, interactive=False,
2948 tobackup=None):
2950 tobackup=None):
2949 """function that actually perform all the actions computed for revert
2951 """function that actually perform all the actions computed for revert
2950
2952
2951 This is an independent function to let extension to plug in and react to
2953 This is an independent function to let extension to plug in and react to
2952 the imminent revert.
2954 the imminent revert.
2953
2955
2954 Make sure you have the working directory locked when calling this function.
2956 Make sure you have the working directory locked when calling this function.
2955 """
2957 """
2956 parent, p2 = parents
2958 parent, p2 = parents
2957 node = ctx.node()
2959 node = ctx.node()
2958 excluded_files = []
2960 excluded_files = []
2959
2961
2960 def checkout(f):
2962 def checkout(f):
2961 fc = ctx[f]
2963 fc = ctx[f]
2962 repo.wwrite(f, fc.data(), fc.flags())
2964 repo.wwrite(f, fc.data(), fc.flags())
2963
2965
2964 def doremove(f):
2966 def doremove(f):
2965 try:
2967 try:
2966 repo.wvfs.unlinkpath(f)
2968 repo.wvfs.unlinkpath(f)
2967 except OSError:
2969 except OSError:
2968 pass
2970 pass
2969 repo.dirstate.remove(f)
2971 repo.dirstate.remove(f)
2970
2972
2971 audit_path = pathutil.pathauditor(repo.root, cached=True)
2973 audit_path = pathutil.pathauditor(repo.root, cached=True)
2972 for f in actions['forget'][0]:
2974 for f in actions['forget'][0]:
2973 if interactive:
2975 if interactive:
2974 choice = repo.ui.promptchoice(
2976 choice = repo.ui.promptchoice(
2975 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
2977 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
2976 if choice == 0:
2978 if choice == 0:
2977 repo.dirstate.drop(f)
2979 repo.dirstate.drop(f)
2978 else:
2980 else:
2979 excluded_files.append(f)
2981 excluded_files.append(f)
2980 else:
2982 else:
2981 repo.dirstate.drop(f)
2983 repo.dirstate.drop(f)
2982 for f in actions['remove'][0]:
2984 for f in actions['remove'][0]:
2983 audit_path(f)
2985 audit_path(f)
2984 if interactive:
2986 if interactive:
2985 choice = repo.ui.promptchoice(
2987 choice = repo.ui.promptchoice(
2986 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
2988 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
2987 if choice == 0:
2989 if choice == 0:
2988 doremove(f)
2990 doremove(f)
2989 else:
2991 else:
2990 excluded_files.append(f)
2992 excluded_files.append(f)
2991 else:
2993 else:
2992 doremove(f)
2994 doremove(f)
2993 for f in actions['drop'][0]:
2995 for f in actions['drop'][0]:
2994 audit_path(f)
2996 audit_path(f)
2995 repo.dirstate.remove(f)
2997 repo.dirstate.remove(f)
2996
2998
2997 normal = None
2999 normal = None
2998 if node == parent:
3000 if node == parent:
2999 # We're reverting to our parent. If possible, we'd like status
3001 # We're reverting to our parent. If possible, we'd like status
3000 # to report the file as clean. We have to use normallookup for
3002 # to report the file as clean. We have to use normallookup for
3001 # merges to avoid losing information about merged/dirty files.
3003 # merges to avoid losing information about merged/dirty files.
3002 if p2 != nullid:
3004 if p2 != nullid:
3003 normal = repo.dirstate.normallookup
3005 normal = repo.dirstate.normallookup
3004 else:
3006 else:
3005 normal = repo.dirstate.normal
3007 normal = repo.dirstate.normal
3006
3008
3007 newlyaddedandmodifiedfiles = set()
3009 newlyaddedandmodifiedfiles = set()
3008 if interactive:
3010 if interactive:
3009 # Prompt the user for changes to revert
3011 # Prompt the user for changes to revert
3010 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3012 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3011 m = scmutil.matchfiles(repo, torevert)
3013 m = scmutil.matchfiles(repo, torevert)
3012 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3014 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3013 diffopts.nodates = True
3015 diffopts.nodates = True
3014 diffopts.git = True
3016 diffopts.git = True
3015 operation = 'discard'
3017 operation = 'discard'
3016 reversehunks = True
3018 reversehunks = True
3017 if node != parent:
3019 if node != parent:
3018 operation = 'apply'
3020 operation = 'apply'
3019 reversehunks = False
3021 reversehunks = False
3020 if reversehunks:
3022 if reversehunks:
3021 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3023 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3022 else:
3024 else:
3023 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3025 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3024 originalchunks = patch.parsepatch(diff)
3026 originalchunks = patch.parsepatch(diff)
3025
3027
3026 try:
3028 try:
3027
3029
3028 chunks, opts = recordfilter(repo.ui, originalchunks,
3030 chunks, opts = recordfilter(repo.ui, originalchunks,
3029 operation=operation)
3031 operation=operation)
3030 if reversehunks:
3032 if reversehunks:
3031 chunks = patch.reversehunks(chunks)
3033 chunks = patch.reversehunks(chunks)
3032
3034
3033 except error.PatchError as err:
3035 except error.PatchError as err:
3034 raise error.Abort(_('error parsing patch: %s') % err)
3036 raise error.Abort(_('error parsing patch: %s') % err)
3035
3037
3036 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3038 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3037 if tobackup is None:
3039 if tobackup is None:
3038 tobackup = set()
3040 tobackup = set()
3039 # Apply changes
3041 # Apply changes
3040 fp = stringio()
3042 fp = stringio()
3041 for c in chunks:
3043 for c in chunks:
3042 # Create a backup file only if this hunk should be backed up
3044 # Create a backup file only if this hunk should be backed up
3043 if ishunk(c) and c.header.filename() in tobackup:
3045 if ishunk(c) and c.header.filename() in tobackup:
3044 abs = c.header.filename()
3046 abs = c.header.filename()
3045 target = repo.wjoin(abs)
3047 target = repo.wjoin(abs)
3046 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3048 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3047 util.copyfile(target, bakname)
3049 util.copyfile(target, bakname)
3048 tobackup.remove(abs)
3050 tobackup.remove(abs)
3049 c.write(fp)
3051 c.write(fp)
3050 dopatch = fp.tell()
3052 dopatch = fp.tell()
3051 fp.seek(0)
3053 fp.seek(0)
3052 if dopatch:
3054 if dopatch:
3053 try:
3055 try:
3054 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3056 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3055 except error.PatchError as err:
3057 except error.PatchError as err:
3056 raise error.Abort(pycompat.bytestr(err))
3058 raise error.Abort(pycompat.bytestr(err))
3057 del fp
3059 del fp
3058 else:
3060 else:
3059 for f in actions['revert'][0]:
3061 for f in actions['revert'][0]:
3060 checkout(f)
3062 checkout(f)
3061 if normal:
3063 if normal:
3062 normal(f)
3064 normal(f)
3063
3065
3064 for f in actions['add'][0]:
3066 for f in actions['add'][0]:
3065 # Don't checkout modified files, they are already created by the diff
3067 # Don't checkout modified files, they are already created by the diff
3066 if f not in newlyaddedandmodifiedfiles:
3068 if f not in newlyaddedandmodifiedfiles:
3067 checkout(f)
3069 checkout(f)
3068 repo.dirstate.add(f)
3070 repo.dirstate.add(f)
3069
3071
3070 normal = repo.dirstate.normallookup
3072 normal = repo.dirstate.normallookup
3071 if node == parent and p2 == nullid:
3073 if node == parent and p2 == nullid:
3072 normal = repo.dirstate.normal
3074 normal = repo.dirstate.normal
3073 for f in actions['undelete'][0]:
3075 for f in actions['undelete'][0]:
3074 checkout(f)
3076 checkout(f)
3075 normal(f)
3077 normal(f)
3076
3078
3077 copied = copies.pathcopies(repo[parent], ctx)
3079 copied = copies.pathcopies(repo[parent], ctx)
3078
3080
3079 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3081 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3080 if f in copied:
3082 if f in copied:
3081 repo.dirstate.copy(copied[f], f)
3083 repo.dirstate.copy(copied[f], f)
3082
3084
3083 class command(registrar.command):
3085 class command(registrar.command):
3084 """deprecated: used registrar.command instead"""
3086 """deprecated: used registrar.command instead"""
3085 def _doregister(self, func, name, *args, **kwargs):
3087 def _doregister(self, func, name, *args, **kwargs):
3086 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3088 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3087 return super(command, self)._doregister(func, name, *args, **kwargs)
3089 return super(command, self)._doregister(func, name, *args, **kwargs)
3088
3090
3089 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3091 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3090 # commands.outgoing. "missing" is "missing" of the result of
3092 # commands.outgoing. "missing" is "missing" of the result of
3091 # "findcommonoutgoing()"
3093 # "findcommonoutgoing()"
3092 outgoinghooks = util.hooks()
3094 outgoinghooks = util.hooks()
3093
3095
3094 # a list of (ui, repo) functions called by commands.summary
3096 # a list of (ui, repo) functions called by commands.summary
3095 summaryhooks = util.hooks()
3097 summaryhooks = util.hooks()
3096
3098
3097 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3099 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3098 #
3100 #
3099 # functions should return tuple of booleans below, if 'changes' is None:
3101 # functions should return tuple of booleans below, if 'changes' is None:
3100 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3102 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3101 #
3103 #
3102 # otherwise, 'changes' is a tuple of tuples below:
3104 # otherwise, 'changes' is a tuple of tuples below:
3103 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3105 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3104 # - (desturl, destbranch, destpeer, outgoing)
3106 # - (desturl, destbranch, destpeer, outgoing)
3105 summaryremotehooks = util.hooks()
3107 summaryremotehooks = util.hooks()
3106
3108
3107 # A list of state files kept by multistep operations like graft.
3109 # A list of state files kept by multistep operations like graft.
3108 # Since graft cannot be aborted, it is considered 'clearable' by update.
3110 # Since graft cannot be aborted, it is considered 'clearable' by update.
3109 # note: bisect is intentionally excluded
3111 # note: bisect is intentionally excluded
3110 # (state file, clearable, allowcommit, error, hint)
3112 # (state file, clearable, allowcommit, error, hint)
3111 unfinishedstates = [
3113 unfinishedstates = [
3112 ('graftstate', True, False, _('graft in progress'),
3114 ('graftstate', True, False, _('graft in progress'),
3113 _("use 'hg graft --continue' or 'hg update' to abort")),
3115 _("use 'hg graft --continue' or 'hg update' to abort")),
3114 ('updatestate', True, False, _('last update was interrupted'),
3116 ('updatestate', True, False, _('last update was interrupted'),
3115 _("use 'hg update' to get a consistent checkout"))
3117 _("use 'hg update' to get a consistent checkout"))
3116 ]
3118 ]
3117
3119
3118 def checkunfinished(repo, commit=False):
3120 def checkunfinished(repo, commit=False):
3119 '''Look for an unfinished multistep operation, like graft, and abort
3121 '''Look for an unfinished multistep operation, like graft, and abort
3120 if found. It's probably good to check this right before
3122 if found. It's probably good to check this right before
3121 bailifchanged().
3123 bailifchanged().
3122 '''
3124 '''
3123 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3125 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3124 if commit and allowcommit:
3126 if commit and allowcommit:
3125 continue
3127 continue
3126 if repo.vfs.exists(f):
3128 if repo.vfs.exists(f):
3127 raise error.Abort(msg, hint=hint)
3129 raise error.Abort(msg, hint=hint)
3128
3130
3129 def clearunfinished(repo):
3131 def clearunfinished(repo):
3130 '''Check for unfinished operations (as above), and clear the ones
3132 '''Check for unfinished operations (as above), and clear the ones
3131 that are clearable.
3133 that are clearable.
3132 '''
3134 '''
3133 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3135 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3134 if not clearable and repo.vfs.exists(f):
3136 if not clearable and repo.vfs.exists(f):
3135 raise error.Abort(msg, hint=hint)
3137 raise error.Abort(msg, hint=hint)
3136 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3138 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3137 if clearable and repo.vfs.exists(f):
3139 if clearable and repo.vfs.exists(f):
3138 util.unlink(repo.vfs.join(f))
3140 util.unlink(repo.vfs.join(f))
3139
3141
3140 afterresolvedstates = [
3142 afterresolvedstates = [
3141 ('graftstate',
3143 ('graftstate',
3142 _('hg graft --continue')),
3144 _('hg graft --continue')),
3143 ]
3145 ]
3144
3146
3145 def howtocontinue(repo):
3147 def howtocontinue(repo):
3146 '''Check for an unfinished operation and return the command to finish
3148 '''Check for an unfinished operation and return the command to finish
3147 it.
3149 it.
3148
3150
3149 afterresolvedstates tuples define a .hg/{file} and the corresponding
3151 afterresolvedstates tuples define a .hg/{file} and the corresponding
3150 command needed to finish it.
3152 command needed to finish it.
3151
3153
3152 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3154 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3153 a boolean.
3155 a boolean.
3154 '''
3156 '''
3155 contmsg = _("continue: %s")
3157 contmsg = _("continue: %s")
3156 for f, msg in afterresolvedstates:
3158 for f, msg in afterresolvedstates:
3157 if repo.vfs.exists(f):
3159 if repo.vfs.exists(f):
3158 return contmsg % msg, True
3160 return contmsg % msg, True
3159 if repo[None].dirty(missing=True, merge=False, branch=False):
3161 if repo[None].dirty(missing=True, merge=False, branch=False):
3160 return contmsg % _("hg commit"), False
3162 return contmsg % _("hg commit"), False
3161 return None, None
3163 return None, None
3162
3164
3163 def checkafterresolved(repo):
3165 def checkafterresolved(repo):
3164 '''Inform the user about the next action after completing hg resolve
3166 '''Inform the user about the next action after completing hg resolve
3165
3167
3166 If there's a matching afterresolvedstates, howtocontinue will yield
3168 If there's a matching afterresolvedstates, howtocontinue will yield
3167 repo.ui.warn as the reporter.
3169 repo.ui.warn as the reporter.
3168
3170
3169 Otherwise, it will yield repo.ui.note.
3171 Otherwise, it will yield repo.ui.note.
3170 '''
3172 '''
3171 msg, warning = howtocontinue(repo)
3173 msg, warning = howtocontinue(repo)
3172 if msg is not None:
3174 if msg is not None:
3173 if warning:
3175 if warning:
3174 repo.ui.warn("%s\n" % msg)
3176 repo.ui.warn("%s\n" % msg)
3175 else:
3177 else:
3176 repo.ui.note("%s\n" % msg)
3178 repo.ui.note("%s\n" % msg)
3177
3179
3178 def wrongtooltocontinue(repo, task):
3180 def wrongtooltocontinue(repo, task):
3179 '''Raise an abort suggesting how to properly continue if there is an
3181 '''Raise an abort suggesting how to properly continue if there is an
3180 active task.
3182 active task.
3181
3183
3182 Uses howtocontinue() to find the active task.
3184 Uses howtocontinue() to find the active task.
3183
3185
3184 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3186 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3185 a hint.
3187 a hint.
3186 '''
3188 '''
3187 after = howtocontinue(repo)
3189 after = howtocontinue(repo)
3188 hint = None
3190 hint = None
3189 if after[1]:
3191 if after[1]:
3190 hint = after[0]
3192 hint = after[0]
3191 raise error.Abort(_('no %s in progress') % task, hint=hint)
3193 raise error.Abort(_('no %s in progress') % task, hint=hint)
3192
3194
3193 class changeset_printer(logcmdutil.changesetprinter):
3195 class changeset_printer(logcmdutil.changesetprinter):
3194
3196
3195 def __init__(self, ui, *args, **kwargs):
3197 def __init__(self, ui, *args, **kwargs):
3196 msg = ("'cmdutil.changeset_printer' is deprecated, "
3198 msg = ("'cmdutil.changeset_printer' is deprecated, "
3197 "use 'logcmdutil.logcmdutil'")
3199 "use 'logcmdutil.logcmdutil'")
3198 ui.deprecwarn(msg, "4.6")
3200 ui.deprecwarn(msg, "4.6")
3199 super(changeset_printer, self).__init__(ui, *args, **kwargs)
3201 super(changeset_printer, self).__init__(ui, *args, **kwargs)
3200
3202
3201 def displaygraph(ui, *args, **kwargs):
3203 def displaygraph(ui, *args, **kwargs):
3202 msg = ("'cmdutil.displaygraph' is deprecated, "
3204 msg = ("'cmdutil.displaygraph' is deprecated, "
3203 "use 'logcmdutil.displaygraph'")
3205 "use 'logcmdutil.displaygraph'")
3204 ui.deprecwarn(msg, "4.6")
3206 ui.deprecwarn(msg, "4.6")
3205 return logcmdutil.displaygraph(ui, *args, **kwargs)
3207 return logcmdutil.displaygraph(ui, *args, **kwargs)
3206
3208
3207 def show_changeset(ui, *args, **kwargs):
3209 def show_changeset(ui, *args, **kwargs):
3208 msg = ("'cmdutil.show_changeset' is deprecated, "
3210 msg = ("'cmdutil.show_changeset' is deprecated, "
3209 "use 'logcmdutil.changesetdisplayer'")
3211 "use 'logcmdutil.changesetdisplayer'")
3210 ui.deprecwarn(msg, "4.6")
3212 ui.deprecwarn(msg, "4.6")
3211 return logcmdutil.changesetdisplayer(ui, *args, **kwargs)
3213 return logcmdutil.changesetdisplayer(ui, *args, **kwargs)
@@ -1,659 +1,692 b''
1 $ hg init
1 $ hg init
2 $ mkdir d1 d1/d11 d2
2 $ mkdir d1 d1/d11 d2
3 $ echo d1/a > d1/a
3 $ echo d1/a > d1/a
4 $ echo d1/ba > d1/ba
4 $ echo d1/ba > d1/ba
5 $ echo d1/a1 > d1/d11/a1
5 $ echo d1/a1 > d1/d11/a1
6 $ echo d1/b > d1/b
6 $ echo d1/b > d1/b
7 $ echo d2/b > d2/b
7 $ echo d2/b > d2/b
8 $ hg add d1/a d1/b d1/ba d1/d11/a1 d2/b
8 $ hg add d1/a d1/b d1/ba d1/d11/a1 d2/b
9 $ hg commit -m "1"
9 $ hg commit -m "1"
10
10
11 rename a single file
11 rename a single file
12
12
13 $ hg rename d1/d11/a1 d2/c
13 $ hg rename d1/d11/a1 d2/c
14 $ hg --config ui.portablefilenames=abort rename d1/a d1/con.xml
14 $ hg --config ui.portablefilenames=abort rename d1/a d1/con.xml
15 abort: filename contains 'con', which is reserved on Windows: d1/con.xml
15 abort: filename contains 'con', which is reserved on Windows: d1/con.xml
16 [255]
16 [255]
17 $ hg sum
17 $ hg sum
18 parent: 0:9b4b6e7b2c26 tip
18 parent: 0:9b4b6e7b2c26 tip
19 1
19 1
20 branch: default
20 branch: default
21 commit: 1 renamed
21 commit: 1 renamed
22 update: (current)
22 update: (current)
23 phases: 1 draft
23 phases: 1 draft
24 $ hg status -C
24 $ hg status -C
25 A d2/c
25 A d2/c
26 d1/d11/a1
26 d1/d11/a1
27 R d1/d11/a1
27 R d1/d11/a1
28 $ hg update -C
28 $ hg update -C
29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 $ rm d2/c
30 $ rm d2/c
31
31
32 rename a single file using absolute paths
32 rename a single file using absolute paths
33
33
34 $ hg rename `pwd`/d1/d11/a1 `pwd`/d2/c
34 $ hg rename `pwd`/d1/d11/a1 `pwd`/d2/c
35 $ hg status -C
35 $ hg status -C
36 A d2/c
36 A d2/c
37 d1/d11/a1
37 d1/d11/a1
38 R d1/d11/a1
38 R d1/d11/a1
39 $ hg update -C
39 $ hg update -C
40 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
41 $ rm d2/c
41 $ rm d2/c
42
42
43 rename --after a single file
43 rename --after a single file
44
44
45 $ mv d1/d11/a1 d2/c
45 $ mv d1/d11/a1 d2/c
46 $ hg rename --after d1/d11/a1 d2/c
46 $ hg rename --after d1/d11/a1 d2/c
47 $ hg status -C
47 $ hg status -C
48 A d2/c
48 A d2/c
49 d1/d11/a1
49 d1/d11/a1
50 R d1/d11/a1
50 R d1/d11/a1
51 $ hg update -C
51 $ hg update -C
52 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
52 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
53 $ rm d2/c
53 $ rm d2/c
54
54
55 rename --after a single file when src and tgt already tracked
55 rename --after a single file when src and tgt already tracked
56
56
57 $ mv d1/d11/a1 d2/c
57 $ mv d1/d11/a1 d2/c
58 $ hg addrem -s 0
58 $ hg addrem -s 0
59 removing d1/d11/a1
59 removing d1/d11/a1
60 adding d2/c
60 adding d2/c
61 $ hg rename --after d1/d11/a1 d2/c
61 $ hg rename --after d1/d11/a1 d2/c
62 $ hg status -C
62 $ hg status -C
63 A d2/c
63 A d2/c
64 d1/d11/a1
64 d1/d11/a1
65 R d1/d11/a1
65 R d1/d11/a1
66 $ hg update -C
66 $ hg update -C
67 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 $ rm d2/c
68 $ rm d2/c
69
69
70 rename --after a single file to a nonexistent target filename
70 rename --after a single file to a nonexistent target filename
71
71
72 $ hg rename --after d1/a dummy
72 $ hg rename --after d1/a dummy
73 d1/a: not recording move - dummy does not exist
73 d1/a: not recording move - dummy does not exist
74
74
75 move a single file to an existing directory
75 move a single file to an existing directory
76
76
77 $ hg rename d1/d11/a1 d2
77 $ hg rename d1/d11/a1 d2
78 $ hg status -C
78 $ hg status -C
79 A d2/a1
79 A d2/a1
80 d1/d11/a1
80 d1/d11/a1
81 R d1/d11/a1
81 R d1/d11/a1
82 $ hg update -C
82 $ hg update -C
83 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 $ rm d2/a1
84 $ rm d2/a1
85
85
86 move --after a single file to an existing directory
86 move --after a single file to an existing directory
87
87
88 $ mv d1/d11/a1 d2
88 $ mv d1/d11/a1 d2
89 $ hg rename --after d1/d11/a1 d2
89 $ hg rename --after d1/d11/a1 d2
90 $ hg status -C
90 $ hg status -C
91 A d2/a1
91 A d2/a1
92 d1/d11/a1
92 d1/d11/a1
93 R d1/d11/a1
93 R d1/d11/a1
94 $ hg update -C
94 $ hg update -C
95 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
95 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
96 $ rm d2/a1
96 $ rm d2/a1
97
97
98 rename a file using a relative path
98 rename a file using a relative path
99
99
100 $ (cd d1/d11; hg rename ../../d2/b e)
100 $ (cd d1/d11; hg rename ../../d2/b e)
101 $ hg status -C
101 $ hg status -C
102 A d1/d11/e
102 A d1/d11/e
103 d2/b
103 d2/b
104 R d2/b
104 R d2/b
105 $ hg update -C
105 $ hg update -C
106 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
106 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
107 $ rm d1/d11/e
107 $ rm d1/d11/e
108
108
109 rename --after a file using a relative path
109 rename --after a file using a relative path
110
110
111 $ (cd d1/d11; mv ../../d2/b e; hg rename --after ../../d2/b e)
111 $ (cd d1/d11; mv ../../d2/b e; hg rename --after ../../d2/b e)
112 $ hg status -C
112 $ hg status -C
113 A d1/d11/e
113 A d1/d11/e
114 d2/b
114 d2/b
115 R d2/b
115 R d2/b
116 $ hg update -C
116 $ hg update -C
117 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 $ rm d1/d11/e
118 $ rm d1/d11/e
119
119
120 rename directory d1 as d3
120 rename directory d1 as d3
121
121
122 $ hg rename d1/ d3
122 $ hg rename d1/ d3
123 moving d1/a to d3/a
123 moving d1/a to d3/a
124 moving d1/b to d3/b
124 moving d1/b to d3/b
125 moving d1/ba to d3/ba
125 moving d1/ba to d3/ba
126 moving d1/d11/a1 to d3/d11/a1
126 moving d1/d11/a1 to d3/d11/a1
127 $ hg status -C
127 $ hg status -C
128 A d3/a
128 A d3/a
129 d1/a
129 d1/a
130 A d3/b
130 A d3/b
131 d1/b
131 d1/b
132 A d3/ba
132 A d3/ba
133 d1/ba
133 d1/ba
134 A d3/d11/a1
134 A d3/d11/a1
135 d1/d11/a1
135 d1/d11/a1
136 R d1/a
136 R d1/a
137 R d1/b
137 R d1/b
138 R d1/ba
138 R d1/ba
139 R d1/d11/a1
139 R d1/d11/a1
140 $ hg update -C
140 $ hg update -C
141 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 $ rm -rf d3
142 $ rm -rf d3
143
143
144 rename --after directory d1 as d3
144 rename --after directory d1 as d3
145
145
146 $ mv d1 d3
146 $ mv d1 d3
147 $ hg rename --after d1 d3
147 $ hg rename --after d1 d3
148 moving d1/a to d3/a
148 moving d1/a to d3/a
149 moving d1/b to d3/b
149 moving d1/b to d3/b
150 moving d1/ba to d3/ba
150 moving d1/ba to d3/ba
151 moving d1/d11/a1 to d3/d11/a1
151 moving d1/d11/a1 to d3/d11/a1
152 $ hg status -C
152 $ hg status -C
153 A d3/a
153 A d3/a
154 d1/a
154 d1/a
155 A d3/b
155 A d3/b
156 d1/b
156 d1/b
157 A d3/ba
157 A d3/ba
158 d1/ba
158 d1/ba
159 A d3/d11/a1
159 A d3/d11/a1
160 d1/d11/a1
160 d1/d11/a1
161 R d1/a
161 R d1/a
162 R d1/b
162 R d1/b
163 R d1/ba
163 R d1/ba
164 R d1/d11/a1
164 R d1/d11/a1
165 $ hg update -C
165 $ hg update -C
166 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
166 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
167 $ rm -rf d3
167 $ rm -rf d3
168
168
169 move a directory using a relative path
169 move a directory using a relative path
170
170
171 $ (cd d2; mkdir d3; hg rename ../d1/d11 d3)
171 $ (cd d2; mkdir d3; hg rename ../d1/d11 d3)
172 moving ../d1/d11/a1 to d3/d11/a1
172 moving ../d1/d11/a1 to d3/d11/a1
173 $ hg status -C
173 $ hg status -C
174 A d2/d3/d11/a1
174 A d2/d3/d11/a1
175 d1/d11/a1
175 d1/d11/a1
176 R d1/d11/a1
176 R d1/d11/a1
177 $ hg update -C
177 $ hg update -C
178 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
178 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
179 $ rm -rf d2/d3
179 $ rm -rf d2/d3
180
180
181 move --after a directory using a relative path
181 move --after a directory using a relative path
182
182
183 $ (cd d2; mkdir d3; mv ../d1/d11 d3; hg rename --after ../d1/d11 d3)
183 $ (cd d2; mkdir d3; mv ../d1/d11 d3; hg rename --after ../d1/d11 d3)
184 moving ../d1/d11/a1 to d3/d11/a1
184 moving ../d1/d11/a1 to d3/d11/a1
185 $ hg status -C
185 $ hg status -C
186 A d2/d3/d11/a1
186 A d2/d3/d11/a1
187 d1/d11/a1
187 d1/d11/a1
188 R d1/d11/a1
188 R d1/d11/a1
189 $ hg update -C
189 $ hg update -C
190 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
190 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 $ rm -rf d2/d3
191 $ rm -rf d2/d3
192
192
193 move directory d1/d11 to an existing directory d2 (removes empty d1)
193 move directory d1/d11 to an existing directory d2 (removes empty d1)
194
194
195 $ hg rename d1/d11/ d2
195 $ hg rename d1/d11/ d2
196 moving d1/d11/a1 to d2/d11/a1
196 moving d1/d11/a1 to d2/d11/a1
197 $ hg status -C
197 $ hg status -C
198 A d2/d11/a1
198 A d2/d11/a1
199 d1/d11/a1
199 d1/d11/a1
200 R d1/d11/a1
200 R d1/d11/a1
201 $ hg update -C
201 $ hg update -C
202 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
202 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
203 $ rm -rf d2/d11
203 $ rm -rf d2/d11
204
204
205 move directories d1 and d2 to a new directory d3
205 move directories d1 and d2 to a new directory d3
206
206
207 $ mkdir d3
207 $ mkdir d3
208 $ hg rename d1 d2 d3
208 $ hg rename d1 d2 d3
209 moving d1/a to d3/d1/a
209 moving d1/a to d3/d1/a
210 moving d1/b to d3/d1/b
210 moving d1/b to d3/d1/b
211 moving d1/ba to d3/d1/ba
211 moving d1/ba to d3/d1/ba
212 moving d1/d11/a1 to d3/d1/d11/a1
212 moving d1/d11/a1 to d3/d1/d11/a1
213 moving d2/b to d3/d2/b
213 moving d2/b to d3/d2/b
214 $ hg status -C
214 $ hg status -C
215 A d3/d1/a
215 A d3/d1/a
216 d1/a
216 d1/a
217 A d3/d1/b
217 A d3/d1/b
218 d1/b
218 d1/b
219 A d3/d1/ba
219 A d3/d1/ba
220 d1/ba
220 d1/ba
221 A d3/d1/d11/a1
221 A d3/d1/d11/a1
222 d1/d11/a1
222 d1/d11/a1
223 A d3/d2/b
223 A d3/d2/b
224 d2/b
224 d2/b
225 R d1/a
225 R d1/a
226 R d1/b
226 R d1/b
227 R d1/ba
227 R d1/ba
228 R d1/d11/a1
228 R d1/d11/a1
229 R d2/b
229 R d2/b
230 $ hg update -C
230 $ hg update -C
231 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
231 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
232 $ rm -rf d3
232 $ rm -rf d3
233
233
234 move --after directories d1 and d2 to a new directory d3
234 move --after directories d1 and d2 to a new directory d3
235
235
236 $ mkdir d3
236 $ mkdir d3
237 $ mv d1 d2 d3
237 $ mv d1 d2 d3
238 $ hg rename --after d1 d2 d3
238 $ hg rename --after d1 d2 d3
239 moving d1/a to d3/d1/a
239 moving d1/a to d3/d1/a
240 moving d1/b to d3/d1/b
240 moving d1/b to d3/d1/b
241 moving d1/ba to d3/d1/ba
241 moving d1/ba to d3/d1/ba
242 moving d1/d11/a1 to d3/d1/d11/a1
242 moving d1/d11/a1 to d3/d1/d11/a1
243 moving d2/b to d3/d2/b
243 moving d2/b to d3/d2/b
244 $ hg status -C
244 $ hg status -C
245 A d3/d1/a
245 A d3/d1/a
246 d1/a
246 d1/a
247 A d3/d1/b
247 A d3/d1/b
248 d1/b
248 d1/b
249 A d3/d1/ba
249 A d3/d1/ba
250 d1/ba
250 d1/ba
251 A d3/d1/d11/a1
251 A d3/d1/d11/a1
252 d1/d11/a1
252 d1/d11/a1
253 A d3/d2/b
253 A d3/d2/b
254 d2/b
254 d2/b
255 R d1/a
255 R d1/a
256 R d1/b
256 R d1/b
257 R d1/ba
257 R d1/ba
258 R d1/d11/a1
258 R d1/d11/a1
259 R d2/b
259 R d2/b
260 $ hg update -C
260 $ hg update -C
261 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
261 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
262 $ rm -rf d3
262 $ rm -rf d3
263
263
264 move everything under directory d1 to existing directory d2, do not
264 move everything under directory d1 to existing directory d2, do not
265 overwrite existing files (d2/b)
265 overwrite existing files (d2/b)
266
266
267 $ hg rename d1/* d2
267 $ hg rename d1/* d2
268 d2/b: not overwriting - file already committed
268 d2/b: not overwriting - file already committed
269 (hg rename --force to replace the file by recording a rename)
269 (hg rename --force to replace the file by recording a rename)
270 moving d1/d11/a1 to d2/d11/a1
270 moving d1/d11/a1 to d2/d11/a1
271 $ hg status -C
271 $ hg status -C
272 A d2/a
272 A d2/a
273 d1/a
273 d1/a
274 A d2/ba
274 A d2/ba
275 d1/ba
275 d1/ba
276 A d2/d11/a1
276 A d2/d11/a1
277 d1/d11/a1
277 d1/d11/a1
278 R d1/a
278 R d1/a
279 R d1/ba
279 R d1/ba
280 R d1/d11/a1
280 R d1/d11/a1
281 $ diff -u d1/b d2/b
281 $ diff -u d1/b d2/b
282 --- d1/b * (glob)
282 --- d1/b * (glob)
283 +++ d2/b * (glob)
283 +++ d2/b * (glob)
284 @@ * (glob)
284 @@ * (glob)
285 -d1/b
285 -d1/b
286 +d2/b
286 +d2/b
287 [1]
287 [1]
288 $ hg update -C
288 $ hg update -C
289 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
289 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
290 $ rm d2/a d2/ba d2/d11/a1
290 $ rm d2/a d2/ba d2/d11/a1
291
291
292 attempt to move one file into a non-existent directory
292 attempt to move one file into a non-existent directory
293
293
294 $ hg rename d1/a dx/
294 $ hg rename d1/a dx/
295 abort: destination dx/ is not a directory
295 abort: destination dx/ is not a directory
296 [255]
296 [255]
297 $ hg status -C
297 $ hg status -C
298 $ hg update -C
298 $ hg update -C
299 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
299 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
300
300
301 attempt to move potentially more than one file into a non-existent directory
301 attempt to move potentially more than one file into a non-existent directory
302
302
303 $ hg rename 'glob:d1/**' dx
303 $ hg rename 'glob:d1/**' dx
304 abort: with multiple sources, destination must be an existing directory
304 abort: with multiple sources, destination must be an existing directory
305 [255]
305 [255]
306
306
307 move every file under d1 to d2/d21
307 move every file under d1 to d2/d21
308
308
309 $ mkdir d2/d21
309 $ mkdir d2/d21
310 $ hg rename 'glob:d1/**' d2/d21
310 $ hg rename 'glob:d1/**' d2/d21
311 moving d1/a to d2/d21/a
311 moving d1/a to d2/d21/a
312 moving d1/b to d2/d21/b
312 moving d1/b to d2/d21/b
313 moving d1/ba to d2/d21/ba
313 moving d1/ba to d2/d21/ba
314 moving d1/d11/a1 to d2/d21/a1
314 moving d1/d11/a1 to d2/d21/a1
315 $ hg status -C
315 $ hg status -C
316 A d2/d21/a
316 A d2/d21/a
317 d1/a
317 d1/a
318 A d2/d21/a1
318 A d2/d21/a1
319 d1/d11/a1
319 d1/d11/a1
320 A d2/d21/b
320 A d2/d21/b
321 d1/b
321 d1/b
322 A d2/d21/ba
322 A d2/d21/ba
323 d1/ba
323 d1/ba
324 R d1/a
324 R d1/a
325 R d1/b
325 R d1/b
326 R d1/ba
326 R d1/ba
327 R d1/d11/a1
327 R d1/d11/a1
328 $ hg update -C
328 $ hg update -C
329 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
329 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
330 $ rm -rf d2/d21
330 $ rm -rf d2/d21
331
331
332 move --after some files under d1 to d2/d21
332 move --after some files under d1 to d2/d21
333
333
334 $ mkdir d2/d21
334 $ mkdir d2/d21
335 $ mv d1/a d1/d11/a1 d2/d21
335 $ mv d1/a d1/d11/a1 d2/d21
336 $ hg rename --after 'glob:d1/**' d2/d21
336 $ hg rename --after 'glob:d1/**' d2/d21
337 moving d1/a to d2/d21/a
337 moving d1/a to d2/d21/a
338 d1/b: not recording move - d2/d21/b does not exist
338 d1/b: not recording move - d2/d21/b does not exist
339 d1/ba: not recording move - d2/d21/ba does not exist
339 d1/ba: not recording move - d2/d21/ba does not exist
340 moving d1/d11/a1 to d2/d21/a1
340 moving d1/d11/a1 to d2/d21/a1
341 $ hg status -C
341 $ hg status -C
342 A d2/d21/a
342 A d2/d21/a
343 d1/a
343 d1/a
344 A d2/d21/a1
344 A d2/d21/a1
345 d1/d11/a1
345 d1/d11/a1
346 R d1/a
346 R d1/a
347 R d1/d11/a1
347 R d1/d11/a1
348 $ hg update -C
348 $ hg update -C
349 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
349 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
350 $ rm -rf d2/d21
350 $ rm -rf d2/d21
351
351
352 move every file under d1 starting with an 'a' to d2/d21 (regexp)
352 move every file under d1 starting with an 'a' to d2/d21 (regexp)
353
353
354 $ mkdir d2/d21
354 $ mkdir d2/d21
355 $ hg rename 're:d1/([^a][^/]*/)*a.*' d2/d21
355 $ hg rename 're:d1/([^a][^/]*/)*a.*' d2/d21
356 moving d1/a to d2/d21/a
356 moving d1/a to d2/d21/a
357 moving d1/d11/a1 to d2/d21/a1
357 moving d1/d11/a1 to d2/d21/a1
358 $ hg status -C
358 $ hg status -C
359 A d2/d21/a
359 A d2/d21/a
360 d1/a
360 d1/a
361 A d2/d21/a1
361 A d2/d21/a1
362 d1/d11/a1
362 d1/d11/a1
363 R d1/a
363 R d1/a
364 R d1/d11/a1
364 R d1/d11/a1
365 $ hg update -C
365 $ hg update -C
366 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
366 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
367 $ rm -rf d2/d21
367 $ rm -rf d2/d21
368
368
369 attempt to overwrite an existing file
369 attempt to overwrite an existing file
370
370
371 $ echo "ca" > d1/ca
371 $ echo "ca" > d1/ca
372 $ hg rename d1/ba d1/ca
372 $ hg rename d1/ba d1/ca
373 d1/ca: not overwriting - file exists
373 d1/ca: not overwriting - file exists
374 (hg rename --after to record the rename)
374 (hg rename --after to record the rename)
375 $ hg status -C
375 $ hg status -C
376 ? d1/ca
376 ? d1/ca
377 $ hg update -C
377 $ hg update -C
378 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
378 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
379
379
380 forced overwrite of an existing file
380 forced overwrite of an existing file
381
381
382 $ echo "ca" > d1/ca
382 $ echo "ca" > d1/ca
383 $ hg rename --force d1/ba d1/ca
383 $ hg rename --force d1/ba d1/ca
384 $ hg status -C
384 $ hg status -C
385 A d1/ca
385 A d1/ca
386 d1/ba
386 d1/ba
387 R d1/ba
387 R d1/ba
388 $ hg update -C
388 $ hg update -C
389 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
389 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
390 $ rm d1/ca
390 $ rm d1/ca
391
391
392 attempt to overwrite an existing broken symlink
392 attempt to overwrite an existing broken symlink
393
393
394 #if symlink
394 #if symlink
395 $ ln -s ba d1/ca
395 $ ln -s ba d1/ca
396 $ hg rename --traceback d1/ba d1/ca
396 $ hg rename --traceback d1/ba d1/ca
397 d1/ca: not overwriting - file exists
397 d1/ca: not overwriting - file exists
398 (hg rename --after to record the rename)
398 (hg rename --after to record the rename)
399 $ hg status -C
399 $ hg status -C
400 ? d1/ca
400 ? d1/ca
401 $ hg update -C
401 $ hg update -C
402 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
402 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
403 $ rm d1/ca
403 $ rm d1/ca
404
404
405 replace a symlink with a file
405 replace a symlink with a file
406
406
407 $ ln -s ba d1/ca
407 $ ln -s ba d1/ca
408 $ hg rename --force d1/ba d1/ca
408 $ hg rename --force d1/ba d1/ca
409 $ hg status -C
409 $ hg status -C
410 A d1/ca
410 A d1/ca
411 d1/ba
411 d1/ba
412 R d1/ba
412 R d1/ba
413 $ hg update -C
413 $ hg update -C
414 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
414 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
415 $ rm d1/ca
415 $ rm d1/ca
416 #endif
416 #endif
417
417
418 do not copy more than one source file to the same destination file
418 do not copy more than one source file to the same destination file
419
419
420 $ mkdir d3
420 $ mkdir d3
421 $ hg rename d1/* d2/* d3
421 $ hg rename d1/* d2/* d3
422 moving d1/d11/a1 to d3/d11/a1
422 moving d1/d11/a1 to d3/d11/a1
423 d3/b: not overwriting - d2/b collides with d1/b
423 d3/b: not overwriting - d2/b collides with d1/b
424 $ hg status -C
424 $ hg status -C
425 A d3/a
425 A d3/a
426 d1/a
426 d1/a
427 A d3/b
427 A d3/b
428 d1/b
428 d1/b
429 A d3/ba
429 A d3/ba
430 d1/ba
430 d1/ba
431 A d3/d11/a1
431 A d3/d11/a1
432 d1/d11/a1
432 d1/d11/a1
433 R d1/a
433 R d1/a
434 R d1/b
434 R d1/b
435 R d1/ba
435 R d1/ba
436 R d1/d11/a1
436 R d1/d11/a1
437 $ hg update -C
437 $ hg update -C
438 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
438 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
439 $ rm -rf d3
439 $ rm -rf d3
440
440
441 move a whole subtree with "hg rename ."
441 move a whole subtree with "hg rename ."
442
442
443 $ mkdir d3
443 $ mkdir d3
444 $ (cd d1; hg rename . ../d3)
444 $ (cd d1; hg rename . ../d3)
445 moving a to ../d3/d1/a
445 moving a to ../d3/d1/a
446 moving b to ../d3/d1/b
446 moving b to ../d3/d1/b
447 moving ba to ../d3/d1/ba
447 moving ba to ../d3/d1/ba
448 moving d11/a1 to ../d3/d1/d11/a1
448 moving d11/a1 to ../d3/d1/d11/a1
449 $ hg status -C
449 $ hg status -C
450 A d3/d1/a
450 A d3/d1/a
451 d1/a
451 d1/a
452 A d3/d1/b
452 A d3/d1/b
453 d1/b
453 d1/b
454 A d3/d1/ba
454 A d3/d1/ba
455 d1/ba
455 d1/ba
456 A d3/d1/d11/a1
456 A d3/d1/d11/a1
457 d1/d11/a1
457 d1/d11/a1
458 R d1/a
458 R d1/a
459 R d1/b
459 R d1/b
460 R d1/ba
460 R d1/ba
461 R d1/d11/a1
461 R d1/d11/a1
462 $ hg update -C
462 $ hg update -C
463 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
463 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
464 $ rm -rf d3
464 $ rm -rf d3
465
465
466 move a whole subtree with "hg rename --after ."
466 move a whole subtree with "hg rename --after ."
467
467
468 $ mkdir d3
468 $ mkdir d3
469 $ mv d1/* d3
469 $ mv d1/* d3
470 $ (cd d1; hg rename --after . ../d3)
470 $ (cd d1; hg rename --after . ../d3)
471 moving a to ../d3/a
471 moving a to ../d3/a
472 moving b to ../d3/b
472 moving b to ../d3/b
473 moving ba to ../d3/ba
473 moving ba to ../d3/ba
474 moving d11/a1 to ../d3/d11/a1
474 moving d11/a1 to ../d3/d11/a1
475 $ hg status -C
475 $ hg status -C
476 A d3/a
476 A d3/a
477 d1/a
477 d1/a
478 A d3/b
478 A d3/b
479 d1/b
479 d1/b
480 A d3/ba
480 A d3/ba
481 d1/ba
481 d1/ba
482 A d3/d11/a1
482 A d3/d11/a1
483 d1/d11/a1
483 d1/d11/a1
484 R d1/a
484 R d1/a
485 R d1/b
485 R d1/b
486 R d1/ba
486 R d1/ba
487 R d1/d11/a1
487 R d1/d11/a1
488 $ hg update -C
488 $ hg update -C
489 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
489 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
490 $ rm -rf d3
490 $ rm -rf d3
491
491
492 move the parent tree with "hg rename .."
492 move the parent tree with "hg rename .."
493
493
494 $ (cd d1/d11; hg rename .. ../../d3)
494 $ (cd d1/d11; hg rename .. ../../d3)
495 moving ../a to ../../d3/a
495 moving ../a to ../../d3/a
496 moving ../b to ../../d3/b
496 moving ../b to ../../d3/b
497 moving ../ba to ../../d3/ba
497 moving ../ba to ../../d3/ba
498 moving a1 to ../../d3/d11/a1
498 moving a1 to ../../d3/d11/a1
499 $ hg status -C
499 $ hg status -C
500 A d3/a
500 A d3/a
501 d1/a
501 d1/a
502 A d3/b
502 A d3/b
503 d1/b
503 d1/b
504 A d3/ba
504 A d3/ba
505 d1/ba
505 d1/ba
506 A d3/d11/a1
506 A d3/d11/a1
507 d1/d11/a1
507 d1/d11/a1
508 R d1/a
508 R d1/a
509 R d1/b
509 R d1/b
510 R d1/ba
510 R d1/ba
511 R d1/d11/a1
511 R d1/d11/a1
512 $ hg update -C
512 $ hg update -C
513 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
513 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
514 $ rm -rf d3
514 $ rm -rf d3
515
515
516 skip removed files
516 skip removed files
517
517
518 $ hg remove d1/b
518 $ hg remove d1/b
519 $ hg rename d1 d3
519 $ hg rename d1 d3
520 moving d1/a to d3/a
520 moving d1/a to d3/a
521 moving d1/ba to d3/ba
521 moving d1/ba to d3/ba
522 moving d1/d11/a1 to d3/d11/a1
522 moving d1/d11/a1 to d3/d11/a1
523 $ hg status -C
523 $ hg status -C
524 A d3/a
524 A d3/a
525 d1/a
525 d1/a
526 A d3/ba
526 A d3/ba
527 d1/ba
527 d1/ba
528 A d3/d11/a1
528 A d3/d11/a1
529 d1/d11/a1
529 d1/d11/a1
530 R d1/a
530 R d1/a
531 R d1/b
531 R d1/b
532 R d1/ba
532 R d1/ba
533 R d1/d11/a1
533 R d1/d11/a1
534 $ hg update -C
534 $ hg update -C
535 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
535 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
536 $ rm -rf d3
536 $ rm -rf d3
537
537
538 transitive rename
538 transitive rename
539
539
540 $ hg rename d1/b d1/bb
540 $ hg rename d1/b d1/bb
541 $ hg rename d1/bb d1/bc
541 $ hg rename d1/bb d1/bc
542 $ hg status -C
542 $ hg status -C
543 A d1/bc
543 A d1/bc
544 d1/b
544 d1/b
545 R d1/b
545 R d1/b
546 $ hg update -C
546 $ hg update -C
547 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
547 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
548 $ rm d1/bc
548 $ rm d1/bc
549
549
550 transitive rename --after
550 transitive rename --after
551
551
552 $ hg rename d1/b d1/bb
552 $ hg rename d1/b d1/bb
553 $ mv d1/bb d1/bc
553 $ mv d1/bb d1/bc
554 $ hg rename --after d1/bb d1/bc
554 $ hg rename --after d1/bb d1/bc
555 $ hg status -C
555 $ hg status -C
556 A d1/bc
556 A d1/bc
557 d1/b
557 d1/b
558 R d1/b
558 R d1/b
559 $ hg update -C
559 $ hg update -C
560 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
560 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
561 $ rm d1/bc
561 $ rm d1/bc
562
562
563 $ echo "# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)"
563 $ echo "# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)"
564 # idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)
564 # idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)
565 $ hg rename d1/b d1/bb
565 $ hg rename d1/b d1/bb
566 $ echo "some stuff added to d1/bb" >> d1/bb
566 $ echo "some stuff added to d1/bb" >> d1/bb
567 $ hg rename d1/bb d1/b
567 $ hg rename d1/bb d1/b
568 $ hg status -C
568 $ hg status -C
569 M d1/b
569 M d1/b
570 $ hg update -C
570 $ hg update -C
571 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
571 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
572
572
573 overwriting with renames (issue1959)
573 overwriting with renames (issue1959)
574
574
575 $ hg rename d1/a d1/c
575 $ hg rename d1/a d1/c
576 $ hg rename d1/b d1/a
576 $ hg rename d1/b d1/a
577 $ hg status -C
577 $ hg status -C
578 M d1/a
578 M d1/a
579 d1/b
579 d1/b
580 A d1/c
580 A d1/c
581 d1/a
581 d1/a
582 R d1/b
582 R d1/b
583 $ hg diff --git
583 $ hg diff --git
584 diff --git a/d1/a b/d1/a
584 diff --git a/d1/a b/d1/a
585 --- a/d1/a
585 --- a/d1/a
586 +++ b/d1/a
586 +++ b/d1/a
587 @@ -1,1 +1,1 @@
587 @@ -1,1 +1,1 @@
588 -d1/a
588 -d1/a
589 +d1/b
589 +d1/b
590 diff --git a/d1/b b/d1/b
590 diff --git a/d1/b b/d1/b
591 deleted file mode 100644
591 deleted file mode 100644
592 --- a/d1/b
592 --- a/d1/b
593 +++ /dev/null
593 +++ /dev/null
594 @@ -1,1 +0,0 @@
594 @@ -1,1 +0,0 @@
595 -d1/b
595 -d1/b
596 diff --git a/d1/a b/d1/c
596 diff --git a/d1/a b/d1/c
597 copy from d1/a
597 copy from d1/a
598 copy to d1/c
598 copy to d1/c
599 $ hg update -C
599 $ hg update -C
600 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
600 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
601 $ rm d1/c # The file was marked as added, so 'hg update' action was 'forget'
601 $ rm d1/c # The file was marked as added, so 'hg update' action was 'forget'
602
602
603 check illegal path components
603 check illegal path components
604
604
605 $ hg rename d1/d11/a1 .hg/foo
605 $ hg rename d1/d11/a1 .hg/foo
606 abort: path contains illegal component: .hg/foo
606 abort: path contains illegal component: .hg/foo
607 [255]
607 [255]
608 $ hg status -C
608 $ hg status -C
609 $ hg rename d1/d11/a1 ../foo
609 $ hg rename d1/d11/a1 ../foo
610 abort: ../foo not under root '$TESTTMP'
610 abort: ../foo not under root '$TESTTMP'
611 [255]
611 [255]
612 $ hg status -C
612 $ hg status -C
613
613
614 $ mv d1/d11/a1 .hg/foo
614 $ mv d1/d11/a1 .hg/foo
615 $ hg rename --after d1/d11/a1 .hg/foo
615 $ hg rename --after d1/d11/a1 .hg/foo
616 abort: path contains illegal component: .hg/foo
616 abort: path contains illegal component: .hg/foo
617 [255]
617 [255]
618 $ hg status -C
618 $ hg status -C
619 ! d1/d11/a1
619 ! d1/d11/a1
620 $ hg update -C
620 $ hg update -C
621 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
621 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
622 $ rm .hg/foo
622 $ rm .hg/foo
623
623
624 $ hg rename d1/d11/a1 .hg
624 $ hg rename d1/d11/a1 .hg
625 abort: path contains illegal component: .hg/a1
625 abort: path contains illegal component: .hg/a1
626 [255]
626 [255]
627 $ hg --config extensions.largefiles= rename d1/d11/a1 .hg
627 $ hg --config extensions.largefiles= rename d1/d11/a1 .hg
628 The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
628 The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
629 abort: path contains illegal component: .hg/a1
629 abort: path contains illegal component: .hg/a1
630 [255]
630 [255]
631 $ hg status -C
631 $ hg status -C
632 $ hg rename d1/d11/a1 ..
632 $ hg rename d1/d11/a1 ..
633 abort: ../a1 not under root '$TESTTMP'
633 abort: ../a1 not under root '$TESTTMP'
634 [255]
634 [255]
635 $ hg --config extensions.largefiles= rename d1/d11/a1 ..
635 $ hg --config extensions.largefiles= rename d1/d11/a1 ..
636 The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
636 The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
637 abort: ../a1 not under root '$TESTTMP'
637 abort: ../a1 not under root '$TESTTMP'
638 [255]
638 [255]
639 $ hg status -C
639 $ hg status -C
640
640
641 $ mv d1/d11/a1 .hg
641 $ mv d1/d11/a1 .hg
642 $ hg rename --after d1/d11/a1 .hg
642 $ hg rename --after d1/d11/a1 .hg
643 abort: path contains illegal component: .hg/a1
643 abort: path contains illegal component: .hg/a1
644 [255]
644 [255]
645 $ hg status -C
645 $ hg status -C
646 ! d1/d11/a1
646 ! d1/d11/a1
647 $ hg update -C
647 $ hg update -C
648 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
648 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
649 $ rm .hg/a1
649 $ rm .hg/a1
650
650
651 $ (cd d1/d11; hg rename ../../d2/b ../../.hg/foo)
651 $ (cd d1/d11; hg rename ../../d2/b ../../.hg/foo)
652 abort: path contains illegal component: .hg/foo
652 abort: path contains illegal component: .hg/foo
653 [255]
653 [255]
654 $ hg status -C
654 $ hg status -C
655 $ (cd d1/d11; hg rename ../../d2/b ../../../foo)
655 $ (cd d1/d11; hg rename ../../d2/b ../../../foo)
656 abort: ../../../foo not under root '$TESTTMP'
656 abort: ../../../foo not under root '$TESTTMP'
657 [255]
657 [255]
658 $ hg status -C
658 $ hg status -C
659
659
660 check that stat information such as mtime is preserved on rename - it's unclear
661 whether the `touch` and `stat` commands are portable, so we mimic them using
662 python. Not all platforms support precision of even one-second granularity, so
663 we allow a rather generous fudge factor here; 1234567890 is 2009, and the
664 primary thing we care about is that it's not the machine's current time;
665 hopefully it's really unlikely for a machine to have such a broken clock that
666 this test fails. :)
667
668 $ mkdir mtime
669 Create the file (as empty), then update its mtime and atime to be 1234567890.
670 >>> import os
671 >>> filename = "mtime/f"
672 >>> mtime = 1234567890
673 >>> open(filename, "w").close()
674 >>> os.utime(filename, (mtime, mtime))
675 $ hg ci -qAm 'add mtime dir'
676 "hg cp" does not preserve the mtime, so it should be newer than the 2009
677 timestamp.
678 $ hg cp -q mtime mtime_cp
679 >>> from __future__ import print_function
680 >>> import os
681 >>> filename = "mtime_cp/f"
682 >>> print(os.stat(filename).st_mtime < 1234567999)
683 False
684 "hg mv" preserves the mtime, so it should be ~equal to the 2009 timestamp
685 (modulo some fudge factor due to not every system supporting 1s-level
686 precision).
687 $ hg mv -q mtime mtime_mv
688 >>> from __future__ import print_function
689 >>> import os
690 >>> filename = "mtime_mv/f"
691 >>> print(os.stat(filename).st_mtime < 1234567999)
692 True
General Comments 0
You need to be logged in to leave comments. Login now