comparison mercurial/wdutil.py @ 14259:df9ccd39828c

patch: move updatedir() from cmdutil into patch Also, create an artificial wdutil.py to avoid import cycles between patch.py and cmdutil.py.
author Patrick Mezard <pmezard@gmail.com>
date Sun, 08 May 2011 17:48:30 +0200
parents
children
comparison
equal deleted inserted replaced
14258:50e3fb2ab9fa 14259:df9ccd39828c
1 # wdutil.py - working dir utilities
2 #
3 # Copyright 2011 Patrick Mezard <pmezard@gmail.com>
4 #
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.
7
8 import glob, os
9 import util, similar, scmutil
10 import match as matchmod
11 from i18n import _
12
13 def expandpats(pats):
14 if not util.expandglobs:
15 return list(pats)
16 ret = []
17 for p in pats:
18 kind, name = matchmod._patsplit(p, None)
19 if kind is None:
20 try:
21 globbed = glob.glob(name)
22 except re.error:
23 globbed = [name]
24 if globbed:
25 ret.extend(globbed)
26 continue
27 ret.append(p)
28 return ret
29
30 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
31 if pats == ("",):
32 pats = []
33 if not globbed and default == 'relpath':
34 pats = expandpats(pats or [])
35 m = matchmod.match(repo.root, repo.getcwd(), pats,
36 opts.get('include'), opts.get('exclude'), default,
37 auditor=repo.auditor)
38 def badfn(f, msg):
39 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
40 m.bad = badfn
41 return m
42
43 def matchall(repo):
44 return matchmod.always(repo.root, repo.getcwd())
45
46 def matchfiles(repo, files):
47 return matchmod.exact(repo.root, repo.getcwd(), files)
48
49 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
50 if dry_run is None:
51 dry_run = opts.get('dry_run')
52 if similarity is None:
53 similarity = float(opts.get('similarity') or 0)
54 # we'd use status here, except handling of symlinks and ignore is tricky
55 added, unknown, deleted, removed = [], [], [], []
56 audit_path = scmutil.pathauditor(repo.root)
57 m = match(repo, pats, opts)
58 for abs in repo.walk(m):
59 target = repo.wjoin(abs)
60 good = True
61 try:
62 audit_path(abs)
63 except (OSError, util.Abort):
64 good = False
65 rel = m.rel(abs)
66 exact = m.exact(abs)
67 if good and abs not in repo.dirstate:
68 unknown.append(abs)
69 if repo.ui.verbose or not exact:
70 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
71 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
72 or (os.path.isdir(target) and not os.path.islink(target))):
73 deleted.append(abs)
74 if repo.ui.verbose or not exact:
75 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
76 # for finding renames
77 elif repo.dirstate[abs] == 'r':
78 removed.append(abs)
79 elif repo.dirstate[abs] == 'a':
80 added.append(abs)
81 copies = {}
82 if similarity > 0:
83 for old, new, score in similar.findrenames(repo,
84 added + unknown, removed + deleted, similarity):
85 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
86 repo.ui.status(_('recording removal of %s as rename to %s '
87 '(%d%% similar)\n') %
88 (m.rel(old), m.rel(new), score * 100))
89 copies[new] = old
90
91 if not dry_run:
92 wctx = repo[None]
93 wlock = repo.wlock()
94 try:
95 wctx.remove(deleted)
96 wctx.add(unknown)
97 for new, old in copies.iteritems():
98 wctx.copy(old, new)
99 finally:
100 wlock.release()
101
102 def updatedir(ui, repo, patches, similarity=0):
103 '''Update dirstate after patch application according to metadata'''
104 if not patches:
105 return []
106 copies = []
107 removes = set()
108 cfiles = patches.keys()
109 cwd = repo.getcwd()
110 if cwd:
111 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
112 for f in patches:
113 gp = patches[f]
114 if not gp:
115 continue
116 if gp.op == 'RENAME':
117 copies.append((gp.oldpath, gp.path))
118 removes.add(gp.oldpath)
119 elif gp.op == 'COPY':
120 copies.append((gp.oldpath, gp.path))
121 elif gp.op == 'DELETE':
122 removes.add(gp.path)
123
124 wctx = repo[None]
125 for src, dst in copies:
126 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
127 if (not similarity) and removes:
128 wctx.remove(sorted(removes), True)
129
130 for f in patches:
131 gp = patches[f]
132 if gp and gp.mode:
133 islink, isexec = gp.mode
134 dst = repo.wjoin(gp.path)
135 # patch won't create empty files
136 if gp.op == 'ADD' and not os.path.lexists(dst):
137 flags = (isexec and 'x' or '') + (islink and 'l' or '')
138 repo.wwrite(gp.path, '', flags)
139 util.setflags(dst, islink, isexec)
140 addremove(repo, cfiles, similarity=similarity)
141 files = patches.keys()
142 files.extend([r for r in removes if r not in files])
143 return sorted(files)
144
145 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
146 """Update the dirstate to reflect the intent of copying src to dst. For
147 different reasons it might not end with dst being marked as copied from src.
148 """
149 origsrc = repo.dirstate.copied(src) or src
150 if dst == origsrc: # copying back a copy?
151 if repo.dirstate[dst] not in 'mn' and not dryrun:
152 repo.dirstate.normallookup(dst)
153 else:
154 if repo.dirstate[origsrc] == 'a' and origsrc == src:
155 if not ui.quiet:
156 ui.warn(_("%s has not been committed yet, so no copy "
157 "data will be stored for %s.\n")
158 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
159 if repo.dirstate[dst] in '?r' and not dryrun:
160 wctx.add([dst])
161 elif not dryrun:
162 wctx.copy(origsrc, dst)