##// END OF EJS Templates
interfaces: mark a few dirstate methods abstract...
Matt Harbison -
r53328:2c8c46c3 default
parent child Browse files
Show More
@@ -1,334 +1,337
1 from __future__ import annotations
1 from __future__ import annotations
2
2
3 import abc
3 import contextlib
4 import contextlib
4 import os
5 import os
5 import typing
6 import typing
6
7
7 from typing import (
8 from typing import (
8 Any,
9 Any,
9 Callable,
10 Callable,
10 Dict,
11 Dict,
11 Iterable,
12 Iterable,
12 Iterator,
13 Iterator,
13 List,
14 List,
14 Optional,
15 Optional,
15 Protocol,
16 Protocol,
16 Tuple,
17 Tuple,
17 )
18 )
18
19
19 if typing.TYPE_CHECKING:
20 if typing.TYPE_CHECKING:
20 # Almost all mercurial modules are only imported in the type checking phase
21 # Almost all mercurial modules are only imported in the type checking phase
21 # to avoid circular imports
22 # to avoid circular imports
22 from .. import (
23 from .. import (
23 match as matchmod,
24 match as matchmod,
24 scmutil,
25 scmutil,
25 transaction as txnmod,
26 transaction as txnmod,
26 )
27 )
27
28
28 # TODO: finish adding type hints
29 # TODO: finish adding type hints
29 AddParentChangeCallbackT = Callable[
30 AddParentChangeCallbackT = Callable[
30 ["idirstate", Tuple[Any, Any], Tuple[Any, Any]], Any
31 ["idirstate", Tuple[Any, Any], Tuple[Any, Any]], Any
31 ]
32 ]
32 """The callback type for dirstate.addparentchangecallback()."""
33 """The callback type for dirstate.addparentchangecallback()."""
33
34
34 # TODO: add a Protocol for dirstatemap.DirStateItem? (It is
35 # TODO: add a Protocol for dirstatemap.DirStateItem? (It is
35 # conditionalized with python or rust implementations. Also,
36 # conditionalized with python or rust implementations. Also,
36 # git.dirstate needs to yield non-None from ``items()``.)
37 # git.dirstate needs to yield non-None from ``items()``.)
37 DirstateItemT = Any # dirstatemap.DirstateItem
38 DirstateItemT = Any # dirstatemap.DirstateItem
38
39
39 IgnoreFileAndLineT = Tuple[Optional[bytes], int, bytes]
40 IgnoreFileAndLineT = Tuple[Optional[bytes], int, bytes]
40 """The return type of dirstate._ignorefileandline(), which holds
41 """The return type of dirstate._ignorefileandline(), which holds
41 ``(file, lineno, originalline)``.
42 ``(file, lineno, originalline)``.
42 """
43 """
43
44
44 FlagFuncFallbackT = Callable[[], "FlagFuncReturnT"]
45 FlagFuncFallbackT = Callable[[], "FlagFuncReturnT"]
45 """The type for the dirstate.flagfunc() fallback function."""
46 """The type for the dirstate.flagfunc() fallback function."""
46
47
47 FlagFuncReturnT = Callable[[bytes], bytes]
48 FlagFuncReturnT = Callable[[bytes], bytes]
48 """The return type of dirstate.flagfunc()."""
49 """The return type of dirstate.flagfunc()."""
49
50
50 # TODO: verify and complete this- it came from a pytype *.pyi file
51 # TODO: verify and complete this- it came from a pytype *.pyi file
51 StatusReturnT = Tuple[Any, scmutil.status, Any]
52 StatusReturnT = Tuple[Any, scmutil.status, Any]
52 """The return type of dirstate.status()."""
53 """The return type of dirstate.status()."""
53
54
54 # TODO: probably doesn't belong here.
55 # TODO: probably doesn't belong here.
55 TransactionT = txnmod.transaction
56 TransactionT = txnmod.transaction
56 """The type for a transaction used with dirstate.
57 """The type for a transaction used with dirstate.
57
58
58 This is meant to help callers avoid having to remember to delay the import
59 This is meant to help callers avoid having to remember to delay the import
59 of the transaction module.
60 of the transaction module.
60 """
61 """
61
62
62 # TODO: The value can also be mercurial.osutil.stat
63 # TODO: The value can also be mercurial.osutil.stat
63 WalkReturnT = Dict[bytes, Optional[os.stat_result]]
64 WalkReturnT = Dict[bytes, Optional[os.stat_result]]
64 """The return type of dirstate.walk().
65 """The return type of dirstate.walk().
65
66
66 The matched files are keyed in the dictionary, mapped to a stat-like object
67 The matched files are keyed in the dictionary, mapped to a stat-like object
67 if the file exists.
68 if the file exists.
68 """
69 """
69
70
70
71
71 class idirstate(Protocol):
72 class idirstate(Protocol):
72 # TODO: convert these constructor args to fields?
73 # TODO: convert these constructor args to fields?
73 # def __init__(
74 # def __init__(
74 # self,
75 # self,
75 # opener,
76 # opener,
76 # ui,
77 # ui,
77 # root,
78 # root,
78 # validate,
79 # validate,
79 # sparsematchfn,
80 # sparsematchfn,
80 # nodeconstants,
81 # nodeconstants,
81 # use_dirstate_v2,
82 # use_dirstate_v2,
82 # use_tracked_hint=False,
83 # use_tracked_hint=False,
83 # ):
84 # ):
84 # """Create a new dirstate object.
85 # """Create a new dirstate object.
85 #
86 #
86 # opener is an open()-like callable that can be used to open the
87 # opener is an open()-like callable that can be used to open the
87 # dirstate file; root is the root of the directory tracked by
88 # dirstate file; root is the root of the directory tracked by
88 # the dirstate.
89 # the dirstate.
89 # """
90 # """
90
91
91 # TODO: all these private methods and attributes should be made
92 # TODO: all these private methods and attributes should be made
92 # public or removed from the interface.
93 # public or removed from the interface.
93
94
94 # TODO: decorate with `@rootcache(b'.hgignore')` like dirstate class?
95 # TODO: decorate with `@rootcache(b'.hgignore')` like dirstate class?
95 @property
96 @property
96 def _ignore(self) -> matchmod.basematcher:
97 def _ignore(self) -> matchmod.basematcher:
97 """Matcher for ignored files."""
98 """Matcher for ignored files."""
98
99
99 @property
100 @property
100 def is_changing_any(self) -> bool:
101 def is_changing_any(self) -> bool:
101 """True if any changes in progress."""
102 """True if any changes in progress."""
102
103
103 @property
104 @property
104 def is_changing_parents(self) -> bool:
105 def is_changing_parents(self) -> bool:
105 """True if parents changes in progress."""
106 """True if parents changes in progress."""
106
107
107 @property
108 @property
108 def is_changing_files(self) -> bool:
109 def is_changing_files(self) -> bool:
109 """True if file tracking changes in progress."""
110 """True if file tracking changes in progress."""
110
111
111 def _ignorefiles(self) -> List[bytes]:
112 def _ignorefiles(self) -> List[bytes]:
112 """Return a list of files containing patterns to ignore."""
113 """Return a list of files containing patterns to ignore."""
113
114
114 def _ignorefileandline(self, f: bytes) -> IgnoreFileAndLineT:
115 def _ignorefileandline(self, f: bytes) -> IgnoreFileAndLineT:
115 """Given a file `f`, return the ignore file and line that ignores it."""
116 """Given a file `f`, return the ignore file and line that ignores it."""
116
117
117 # TODO: decorate with `@util.propertycache` like dirstate class?
118 # TODO: decorate with `@util.propertycache` like dirstate class?
118 # (can't because circular import)
119 # (can't because circular import)
119 @property
120 @property
120 def _checklink(self) -> bool:
121 def _checklink(self) -> bool:
121 """Callable for checking symlinks.""" # TODO: this comment looks stale
122 """Callable for checking symlinks.""" # TODO: this comment looks stale
122
123
123 # TODO: decorate with `@util.propertycache` like dirstate class?
124 # TODO: decorate with `@util.propertycache` like dirstate class?
124 # (can't because circular import)
125 # (can't because circular import)
125 @property
126 @property
126 def _checkexec(self) -> bool:
127 def _checkexec(self) -> bool:
127 """Callable for checking exec bits.""" # TODO: this comment looks stale
128 """Callable for checking exec bits.""" # TODO: this comment looks stale
128
129
129 @contextlib.contextmanager
130 @contextlib.contextmanager
131 @abc.abstractmethod
130 def changing_parents(self, repo) -> Iterator: # TODO: typehint this
132 def changing_parents(self, repo) -> Iterator: # TODO: typehint this
131 """Context manager for handling dirstate parents.
133 """Context manager for handling dirstate parents.
132
134
133 If an exception occurs in the scope of the context manager,
135 If an exception occurs in the scope of the context manager,
134 the incoherent dirstate won't be written when wlock is
136 the incoherent dirstate won't be written when wlock is
135 released.
137 released.
136 """
138 """
137
139
138 @contextlib.contextmanager
140 @contextlib.contextmanager
141 @abc.abstractmethod
139 def changing_files(self, repo) -> Iterator: # TODO: typehint this
142 def changing_files(self, repo) -> Iterator: # TODO: typehint this
140 """Context manager for handling dirstate files.
143 """Context manager for handling dirstate files.
141
144
142 If an exception occurs in the scope of the context manager,
145 If an exception occurs in the scope of the context manager,
143 the incoherent dirstate won't be written when wlock is
146 the incoherent dirstate won't be written when wlock is
144 released.
147 released.
145 """
148 """
146
149
147 def hasdir(self, d: bytes) -> bool:
150 def hasdir(self, d: bytes) -> bool:
148 pass
151 pass
149
152
150 def flagfunc(self, buildfallback: FlagFuncFallbackT) -> FlagFuncReturnT:
153 def flagfunc(self, buildfallback: FlagFuncFallbackT) -> FlagFuncReturnT:
151 """build a callable that returns flags associated with a filename
154 """build a callable that returns flags associated with a filename
152
155
153 The information is extracted from three possible layers:
156 The information is extracted from three possible layers:
154 1. the file system if it supports the information
157 1. the file system if it supports the information
155 2. the "fallback" information stored in the dirstate if any
158 2. the "fallback" information stored in the dirstate if any
156 3. a more expensive mechanism inferring the flags from the parents.
159 3. a more expensive mechanism inferring the flags from the parents.
157 """
160 """
158
161
159 def getcwd(self) -> bytes:
162 def getcwd(self) -> bytes:
160 """Return the path from which a canonical path is calculated.
163 """Return the path from which a canonical path is calculated.
161
164
162 This path should be used to resolve file patterns or to convert
165 This path should be used to resolve file patterns or to convert
163 canonical paths back to file paths for display. It shouldn't be
166 canonical paths back to file paths for display. It shouldn't be
164 used to get real file paths. Use vfs functions instead.
167 used to get real file paths. Use vfs functions instead.
165 """
168 """
166
169
167 def pathto(self, f: bytes, cwd: Optional[bytes] = None) -> bytes:
170 def pathto(self, f: bytes, cwd: Optional[bytes] = None) -> bytes:
168 pass
171 pass
169
172
170 def get_entry(self, path: bytes) -> DirstateItemT:
173 def get_entry(self, path: bytes) -> DirstateItemT:
171 """return a DirstateItem for the associated path"""
174 """return a DirstateItem for the associated path"""
172
175
173 def __contains__(self, key: Any) -> bool:
176 def __contains__(self, key: Any) -> bool:
174 """Check if bytestring `key` is known to the dirstate."""
177 """Check if bytestring `key` is known to the dirstate."""
175
178
176 def __iter__(self) -> Iterator[bytes]:
179 def __iter__(self) -> Iterator[bytes]:
177 """Iterate the dirstate's contained filenames as bytestrings."""
180 """Iterate the dirstate's contained filenames as bytestrings."""
178
181
179 def items(self) -> Iterator[Tuple[bytes, DirstateItemT]]:
182 def items(self) -> Iterator[Tuple[bytes, DirstateItemT]]:
180 """Iterate the dirstate's entries as (filename, DirstateItem.
183 """Iterate the dirstate's entries as (filename, DirstateItem.
181
184
182 As usual, filename is a bytestring.
185 As usual, filename is a bytestring.
183 """
186 """
184
187
185 iteritems = items
188 iteritems = items
186
189
187 def parents(self) -> List[bytes]:
190 def parents(self) -> List[bytes]:
188 pass
191 pass
189
192
190 def p1(self) -> bytes:
193 def p1(self) -> bytes:
191 pass
194 pass
192
195
193 def p2(self) -> bytes:
196 def p2(self) -> bytes:
194 pass
197 pass
195
198
196 def branch(self) -> bytes:
199 def branch(self) -> bytes:
197 pass
200 pass
198
201
199 # TODO: typehint the return. It's a copies Map of some sort.
202 # TODO: typehint the return. It's a copies Map of some sort.
200 def setparents(self, p1: bytes, p2: Optional[bytes] = None):
203 def setparents(self, p1: bytes, p2: Optional[bytes] = None):
201 """Set dirstate parents to p1 and p2.
204 """Set dirstate parents to p1 and p2.
202
205
203 When moving from two parents to one, "merged" entries a
206 When moving from two parents to one, "merged" entries a
204 adjusted to normal and previous copy records discarded and
207 adjusted to normal and previous copy records discarded and
205 returned by the call.
208 returned by the call.
206
209
207 See localrepo.setparents()
210 See localrepo.setparents()
208 """
211 """
209
212
210 def setbranch(
213 def setbranch(
211 self, branch: bytes, transaction: Optional[TransactionT]
214 self, branch: bytes, transaction: Optional[TransactionT]
212 ) -> None:
215 ) -> None:
213 pass
216 pass
214
217
215 def invalidate(self) -> None:
218 def invalidate(self) -> None:
216 """Causes the next access to reread the dirstate.
219 """Causes the next access to reread the dirstate.
217
220
218 This is different from localrepo.invalidatedirstate() because it always
221 This is different from localrepo.invalidatedirstate() because it always
219 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
222 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
220 check whether the dirstate has changed before rereading it."""
223 check whether the dirstate has changed before rereading it."""
221
224
222 def copy(self, source: Optional[bytes], dest: bytes) -> None:
225 def copy(self, source: Optional[bytes], dest: bytes) -> None:
223 """Mark dest as a copy of source. Unmark dest if source is None."""
226 """Mark dest as a copy of source. Unmark dest if source is None."""
224
227
225 def copied(self, file: bytes) -> Optional[bytes]:
228 def copied(self, file: bytes) -> Optional[bytes]:
226 pass
229 pass
227
230
228 def copies(self) -> Dict[bytes, bytes]:
231 def copies(self) -> Dict[bytes, bytes]:
229 pass
232 pass
230
233
231 def normalize(
234 def normalize(
232 self, path: bytes, isknown: bool = False, ignoremissing: bool = False
235 self, path: bytes, isknown: bool = False, ignoremissing: bool = False
233 ) -> bytes:
236 ) -> bytes:
234 """
237 """
235 normalize the case of a pathname when on a casefolding filesystem
238 normalize the case of a pathname when on a casefolding filesystem
236
239
237 isknown specifies whether the filename came from walking the
240 isknown specifies whether the filename came from walking the
238 disk, to avoid extra filesystem access.
241 disk, to avoid extra filesystem access.
239
242
240 If ignoremissing is True, missing path are returned
243 If ignoremissing is True, missing path are returned
241 unchanged. Otherwise, we try harder to normalize possibly
244 unchanged. Otherwise, we try harder to normalize possibly
242 existing path components.
245 existing path components.
243
246
244 The normalized case is determined based on the following precedence:
247 The normalized case is determined based on the following precedence:
245
248
246 - version of name already stored in the dirstate
249 - version of name already stored in the dirstate
247 - version of name stored on disk
250 - version of name stored on disk
248 - version provided via command arguments
251 - version provided via command arguments
249 """
252 """
250
253
251 def clear(self) -> None:
254 def clear(self) -> None:
252 pass
255 pass
253
256
254 def rebuild(
257 def rebuild(
255 self,
258 self,
256 parent: bytes,
259 parent: bytes,
257 allfiles: Iterable[bytes], # TODO: more than iterable? (uses len())
260 allfiles: Iterable[bytes], # TODO: more than iterable? (uses len())
258 changedfiles: Optional[Iterable[bytes]] = None,
261 changedfiles: Optional[Iterable[bytes]] = None,
259 ) -> None:
262 ) -> None:
260 pass
263 pass
261
264
262 def write(self, tr: Optional[TransactionT]) -> None:
265 def write(self, tr: Optional[TransactionT]) -> None:
263 pass
266 pass
264
267
265 def addparentchangecallback(
268 def addparentchangecallback(
266 self, category: bytes, callback: AddParentChangeCallbackT
269 self, category: bytes, callback: AddParentChangeCallbackT
267 ) -> None:
270 ) -> None:
268 """add a callback to be called when the wd parents are changed
271 """add a callback to be called when the wd parents are changed
269
272
270 Callback will be called with the following arguments:
273 Callback will be called with the following arguments:
271 dirstate, (oldp1, oldp2), (newp1, newp2)
274 dirstate, (oldp1, oldp2), (newp1, newp2)
272
275
273 Category is a unique identifier to allow overwriting an old callback
276 Category is a unique identifier to allow overwriting an old callback
274 with a newer callback.
277 with a newer callback.
275 """
278 """
276
279
277 def walk(
280 def walk(
278 self,
281 self,
279 match: matchmod.basematcher,
282 match: matchmod.basematcher,
280 subrepos: Any, # TODO: figure out what this is
283 subrepos: Any, # TODO: figure out what this is
281 unknown: bool,
284 unknown: bool,
282 ignored: bool,
285 ignored: bool,
283 full: bool = True,
286 full: bool = True,
284 ) -> WalkReturnT:
287 ) -> WalkReturnT:
285 """
288 """
286 Walk recursively through the directory tree, finding all files
289 Walk recursively through the directory tree, finding all files
287 matched by match.
290 matched by match.
288
291
289 If full is False, maybe skip some known-clean files.
292 If full is False, maybe skip some known-clean files.
290
293
291 Return a dict mapping filename to stat-like object (either
294 Return a dict mapping filename to stat-like object (either
292 mercurial.osutil.stat instance or return value of os.stat()).
295 mercurial.osutil.stat instance or return value of os.stat()).
293
296
294 """
297 """
295
298
296 def status(
299 def status(
297 self,
300 self,
298 match: matchmod.basematcher,
301 match: matchmod.basematcher,
299 subrepos: bool,
302 subrepos: bool,
300 ignored: bool,
303 ignored: bool,
301 clean: bool,
304 clean: bool,
302 unknown: bool,
305 unknown: bool,
303 ) -> StatusReturnT:
306 ) -> StatusReturnT:
304 """Determine the status of the working copy relative to the
307 """Determine the status of the working copy relative to the
305 dirstate and return a pair of (unsure, status), where status is of type
308 dirstate and return a pair of (unsure, status), where status is of type
306 scmutil.status and:
309 scmutil.status and:
307
310
308 unsure:
311 unsure:
309 files that might have been modified since the dirstate was
312 files that might have been modified since the dirstate was
310 written, but need to be read to be sure (size is the same
313 written, but need to be read to be sure (size is the same
311 but mtime differs)
314 but mtime differs)
312 status.modified:
315 status.modified:
313 files that have definitely been modified since the dirstate
316 files that have definitely been modified since the dirstate
314 was written (different size or mode)
317 was written (different size or mode)
315 status.clean:
318 status.clean:
316 files that have definitely not been modified since the
319 files that have definitely not been modified since the
317 dirstate was written
320 dirstate was written
318 """
321 """
319
322
320 # TODO: could return a list, except git.dirstate is a generator
323 # TODO: could return a list, except git.dirstate is a generator
321
324
322 def matches(self, match: matchmod.basematcher) -> Iterable[bytes]:
325 def matches(self, match: matchmod.basematcher) -> Iterable[bytes]:
323 """
326 """
324 return files in the dirstate (in whatever state) filtered by match
327 return files in the dirstate (in whatever state) filtered by match
325 """
328 """
326
329
327 # TODO: finish adding typehints here, and to subclasses
330 # TODO: finish adding typehints here, and to subclasses
328
331
329 def verify(
332 def verify(
330 self, m1, m2, p1: bytes, narrow_matcher: Optional[Any] = None
333 self, m1, m2, p1: bytes, narrow_matcher: Optional[Any] = None
331 ) -> Iterator[bytes]:
334 ) -> Iterator[bytes]:
332 """
335 """
333 check the dirstate contents against the parent manifest and yield errors
336 check the dirstate contents against the parent manifest and yield errors
334 """
337 """
General Comments 0
You need to be logged in to leave comments. Login now