1 """ This extension provides direct access |
|
2 It is the ability to refer and access hidden sha in commands provided that you |
|
3 know their value. |
|
4 For example hg log -r xxx where xxx is a commit has should work whether xxx is |
|
5 hidden or not as we assume that the user knows what he is doing when referring |
|
6 to xxx. |
|
7 """ |
|
8 from mercurial import extensions |
|
9 from mercurial import repoview |
|
10 from mercurial import branchmap |
|
11 from mercurial import registrar |
|
12 from mercurial import revset |
|
13 from mercurial import error |
|
14 from mercurial import commands |
|
15 from mercurial import hg |
|
16 from mercurial import util |
|
17 from mercurial.i18n import _ |
|
18 |
|
19 cmdtable = {} |
|
20 |
|
21 if util.safehasattr(registrar, 'command'): |
|
22 command = registrar.command(cmdtable) |
|
23 else: # compat with hg < 4.3 |
|
24 from mercurial import cmdutil |
|
25 command = cmdutil.command(cmdtable) |
|
26 |
|
27 if util.safehasattr(registrar, 'configitem'): |
|
28 configtable = {} |
|
29 configitem = registrar.configitem(configtable) |
|
30 |
|
31 configitem('directaccess', 'loadsafter', |
|
32 default=[], |
|
33 ) |
|
34 |
|
35 # By default, all the commands have directaccess with warnings |
|
36 # List of commands that have no directaccess and directaccess with no warning |
|
37 directaccesslevel = [ |
|
38 # Format: |
|
39 # ('nowarning', 'evolve', 'prune'), |
|
40 # means: no directaccess warning, for the command in evolve named prune |
|
41 # |
|
42 # ('error', None, 'serve'), |
|
43 # means: no directaccess for the command in core named serve |
|
44 # |
|
45 # The list is ordered alphabetically by command names, starting with all |
|
46 # the commands in core then all the commands in the extensions |
|
47 # |
|
48 # The general guideline is: |
|
49 # - remove directaccess warnings for read only commands |
|
50 # - no direct access for commands with consequences outside of the repo |
|
51 # - leave directaccess warnings for all the other commands |
|
52 # |
|
53 ('nowarning', None, 'annotate'), |
|
54 ('nowarning', None, 'archive'), |
|
55 ('nowarning', None, 'bisect'), |
|
56 ('nowarning', None, 'bookmarks'), |
|
57 ('nowarning', None, 'bundle'), |
|
58 ('nowarning', None, 'cat'), |
|
59 ('nowarning', None, 'diff'), |
|
60 ('nowarning', None, 'export'), |
|
61 ('nowarning', None, 'identify'), |
|
62 ('nowarning', None, 'incoming'), |
|
63 ('nowarning', None, 'log'), |
|
64 ('nowarning', None, 'manifest'), |
|
65 ('error', None, 'outgoing'), # confusing if push errors and not outgoing |
|
66 ('error', None, 'push'), # destructive |
|
67 ('nowarning', None, 'revert'), |
|
68 ('error', None, 'serve'), |
|
69 ('nowarning', None, 'tags'), |
|
70 ('nowarning', None, 'unbundle'), |
|
71 ('nowarning', None, 'update'), |
|
72 ] |
|
73 |
|
74 def reposetup(ui, repo): |
|
75 repo._explicitaccess = set() |
|
76 |
|
77 def _computehidden(repo): |
|
78 hidden = repoview.filterrevs(repo, 'visible') |
|
79 cl = repo.changelog |
|
80 dynamic = hidden & repo._explicitaccess |
|
81 if dynamic: |
|
82 blocked = cl.ancestors(dynamic, inclusive=True) |
|
83 hidden = frozenset(r for r in hidden if r not in blocked) |
|
84 return hidden |
|
85 |
|
86 def setupdirectaccess(): |
|
87 """ Add two new filtername that behave like visible to provide direct access |
|
88 and direct access with warning. Wraps the commands to setup direct access |
|
89 """ |
|
90 repoview.filtertable.update({'visible-directaccess-nowarn': _computehidden}) |
|
91 repoview.filtertable.update({'visible-directaccess-warn': _computehidden}) |
|
92 branchmap.subsettable['visible-directaccess-nowarn'] = 'visible' |
|
93 branchmap.subsettable['visible-directaccess-warn'] = 'visible' |
|
94 |
|
95 for warn, ext, cmd in directaccesslevel: |
|
96 try: |
|
97 cmdtable = extensions.find(ext).cmdtable if ext else commands.table |
|
98 wrapper = wrapwitherror if warn == 'error' else wrapwithoutwarning |
|
99 extensions.wrapcommand(cmdtable, cmd, wrapper) |
|
100 except (error.UnknownCommand, KeyError): |
|
101 pass |
|
102 |
|
103 def wrapwitherror(orig, ui, repo, *args, **kwargs): |
|
104 if repo and repo.filtername == 'visible-directaccess-warn': |
|
105 repo = repo.filtered('visible') |
|
106 return orig(ui, repo, *args, **kwargs) |
|
107 |
|
108 def wrapwithoutwarning(orig, ui, repo, *args, **kwargs): |
|
109 if repo and repo.filtername == 'visible-directaccess-warn': |
|
110 repo = repo.filtered("visible-directaccess-nowarn") |
|
111 return orig(ui, repo, *args, **kwargs) |
|
112 |
|
113 def uisetup(ui): |
|
114 """ Change ordering of extensions to ensure that directaccess extsetup comes |
|
115 after the one of the extensions in the loadsafter list """ |
|
116 loadsafter = ui.configlist('directaccess', 'loadsafter') |
|
117 order = list(extensions._order) |
|
118 directaccesidx = order.index('directaccess') |
|
119 |
|
120 # The min idx for directaccess to load after all the extensions in loadafter |
|
121 minidxdirectaccess = directaccesidx |
|
122 |
|
123 for ext in loadsafter: |
|
124 try: |
|
125 minidxdirectaccess = max(minidxdirectaccess, order.index(ext)) |
|
126 except ValueError: |
|
127 pass # extension not loaded |
|
128 |
|
129 if minidxdirectaccess > directaccesidx: |
|
130 order.insert(minidxdirectaccess + 1, 'directaccess') |
|
131 order.remove('directaccess') |
|
132 extensions._order = order |
|
133 |
|
134 def _repository(orig, *args, **kwargs): |
|
135 """Make visible-directaccess-warn the default filter for new repos""" |
|
136 repo = orig(*args, **kwargs) |
|
137 return repo.filtered("visible-directaccess-warn") |
|
138 |
|
139 def extsetup(ui): |
|
140 extensions.wrapfunction(revset, 'posttreebuilthook', _posttreebuilthook) |
|
141 extensions.wrapfunction(hg, 'repository', _repository) |
|
142 setupdirectaccess() |
|
143 |
|
144 hashre = util.re.compile('[0-9a-fA-F]{1,40}') |
|
145 |
|
146 _listtuple = ('symbol', '_list') |
|
147 |
|
148 def _ishashsymbol(symbol, maxrev): |
|
149 # Returns true if symbol looks like a hash |
|
150 try: |
|
151 n = int(symbol) |
|
152 if n <= maxrev: |
|
153 # It's a rev number |
|
154 return False |
|
155 except ValueError: |
|
156 pass |
|
157 return hashre.match(symbol) |
|
158 |
|
159 def gethashsymbols(tree, maxrev): |
|
160 # Returns the list of symbols of the tree that look like hashes |
|
161 # for example for the revset 3::abe3ff it will return ('abe3ff') |
|
162 if not tree: |
|
163 return [] |
|
164 |
|
165 results = [] |
|
166 if len(tree) in (2, 3) and tree[0] == "symbol": |
|
167 results.append(tree[1]) |
|
168 elif tree[0] == "func" and tree[1] == _listtuple: |
|
169 # the optimiser will group sequence of hash request |
|
170 results += tree[2][1].split('\0') |
|
171 elif len(tree) >= 2: |
|
172 for subtree in tree[1:]: |
|
173 results += gethashsymbols(subtree, maxrev) |
|
174 # return directly, we don't need to filter symbols again |
|
175 return results |
|
176 return [s for s in results if _ishashsymbol(s, maxrev)] |
|
177 |
|
178 def _posttreebuilthook(orig, tree, repo): |
|
179 # This is use to enabled direct hash access |
|
180 # We extract the symbols that look like hashes and add them to the |
|
181 # explicitaccess set |
|
182 orig(tree, repo) |
|
183 filternm = "" |
|
184 if repo is not None: |
|
185 filternm = repo.filtername |
|
186 if filternm is not None and filternm.startswith('visible-directaccess'): |
|
187 prelength = len(repo._explicitaccess) |
|
188 accessbefore = set(repo._explicitaccess) |
|
189 cl = repo.unfiltered().changelog |
|
190 repo.symbols = gethashsymbols(tree, len(cl)) |
|
191 for node in repo.symbols: |
|
192 try: |
|
193 node = cl._partialmatch(node) |
|
194 except error.LookupError: |
|
195 node = None |
|
196 if node is not None: |
|
197 rev = cl.rev(node) |
|
198 if rev not in repo.changelog: |
|
199 repo._explicitaccess.add(rev) |
|
200 if prelength != len(repo._explicitaccess): |
|
201 if repo.filtername != 'visible-directaccess-nowarn': |
|
202 unhiddencommits = repo._explicitaccess - accessbefore |
|
203 repo.ui.warn(_("Warning: accessing hidden changesets %s " |
|
204 "for write operation\n") % |
|
205 (",".join([str(repo.unfiltered()[l]) |
|
206 for l in unhiddencommits]))) |
|
207 repo.invalidatevolatilesets() |
|