##// END OF EJS Templates
git: correct some signature mismatches between dirstate and the Protocol class...
Matt Harbison -
r52817:51be8bf8 default
parent child Browse files
Show More
@@ -1,397 +1,399
1 1 from __future__ import annotations
2 2
3 3 import contextlib
4 4 import os
5 5
6 6 from mercurial.node import sha1nodeconstants
7 7 from mercurial import (
8 8 dirstatemap,
9 9 error,
10 10 extensions,
11 11 match as matchmod,
12 12 pycompat,
13 13 scmutil,
14 14 util,
15 15 )
16 16 from mercurial.dirstateutils import (
17 17 timestamp,
18 18 )
19 19 from mercurial.interfaces import (
20 20 dirstate as intdirstate,
21 21 util as interfaceutil,
22 22 )
23 23
24 24 from . import gitutil
25 25
26 26
27 27 DirstateItem = dirstatemap.DirstateItem
28 28 propertycache = util.propertycache
29 29 pygit2 = gitutil.get_pygit2()
30 30
31 31
32 32 def readpatternfile(orig, filepath, warn, sourceinfo=False):
33 33 if not (b'info/exclude' in filepath or filepath.endswith(b'.gitignore')):
34 34 return orig(filepath, warn, sourceinfo=False)
35 35 result = []
36 36 warnings = []
37 37 with open(filepath, 'rb') as fp:
38 38 for l in fp:
39 39 l = l.strip()
40 40 if not l or l.startswith(b'#'):
41 41 continue
42 42 if l.startswith(b'!'):
43 43 warnings.append(b'unsupported ignore pattern %s' % l)
44 44 continue
45 45 if l.startswith(b'/'):
46 46 result.append(b'rootglob:' + l[1:])
47 47 else:
48 48 result.append(b'relglob:' + l)
49 49 return result, warnings
50 50
51 51
52 52 extensions.wrapfunction(matchmod, 'readpatternfile', readpatternfile)
53 53
54 54
55 55 _STATUS_MAP = {}
56 56 if pygit2:
57 57 _STATUS_MAP = {
58 58 pygit2.GIT_STATUS_CONFLICTED: b'm',
59 59 pygit2.GIT_STATUS_CURRENT: b'n',
60 60 pygit2.GIT_STATUS_IGNORED: b'?',
61 61 pygit2.GIT_STATUS_INDEX_DELETED: b'r',
62 62 pygit2.GIT_STATUS_INDEX_MODIFIED: b'n',
63 63 pygit2.GIT_STATUS_INDEX_NEW: b'a',
64 64 pygit2.GIT_STATUS_INDEX_RENAMED: b'a',
65 65 pygit2.GIT_STATUS_INDEX_TYPECHANGE: b'n',
66 66 pygit2.GIT_STATUS_WT_DELETED: b'r',
67 67 pygit2.GIT_STATUS_WT_MODIFIED: b'n',
68 68 pygit2.GIT_STATUS_WT_NEW: b'?',
69 69 pygit2.GIT_STATUS_WT_RENAMED: b'a',
70 70 pygit2.GIT_STATUS_WT_TYPECHANGE: b'n',
71 71 pygit2.GIT_STATUS_WT_UNREADABLE: b'?',
72 72 pygit2.GIT_STATUS_INDEX_MODIFIED | pygit2.GIT_STATUS_WT_MODIFIED: b'm',
73 73 }
74 74
75 75
76 76 @interfaceutil.implementer(intdirstate.idirstate)
77 77 class gitdirstate:
78 78 def __init__(self, ui, vfs, gitrepo, use_dirstate_v2):
79 79 self._ui = ui
80 80 self._root = os.path.dirname(vfs.base)
81 81 self._opener = vfs
82 82 self.git = gitrepo
83 83 self._plchangecallbacks = {}
84 84 # TODO: context.poststatusfixup is bad and uses this attribute
85 85 self._dirty = False
86 86 self._mapcls = dirstatemap.dirstatemap
87 87 self._use_dirstate_v2 = use_dirstate_v2
88 88
89 89 @propertycache
90 90 def _map(self):
91 91 """Return the dirstate contents (see documentation for dirstatemap)."""
92 92 self._map = self._mapcls(
93 93 self._ui,
94 94 self._opener,
95 95 self._root,
96 96 sha1nodeconstants,
97 97 self._use_dirstate_v2,
98 98 )
99 99 return self._map
100 100
101 101 def p1(self):
102 102 try:
103 103 return self.git.head.peel().id.raw
104 104 except pygit2.GitError:
105 105 # Typically happens when peeling HEAD fails, as in an
106 106 # empty repository.
107 107 return sha1nodeconstants.nullid
108 108
109 109 def p2(self):
110 110 # TODO: MERGE_HEAD? something like that, right?
111 111 return sha1nodeconstants.nullid
112 112
113 113 def setparents(self, p1, p2=None):
114 114 if p2 is None:
115 115 p2 = sha1nodeconstants.nullid
116 116 assert p2 == sha1nodeconstants.nullid, b'TODO merging support'
117 117 self.git.head.set_target(gitutil.togitnode(p1))
118 118
119 119 @util.propertycache
120 120 def identity(self):
121 121 return util.filestat.frompath(
122 122 os.path.join(self._root, b'.git', b'index')
123 123 )
124 124
125 125 def branch(self):
126 126 return b'default'
127 127
128 128 def parents(self):
129 129 # TODO how on earth do we find p2 if a merge is in flight?
130 130 return self.p1(), sha1nodeconstants.nullid
131 131
132 132 def __iter__(self):
133 133 return (pycompat.fsencode(f.path) for f in self.git.index)
134 134
135 135 def items(self):
136 136 for ie in self.git.index:
137 137 yield ie.path, None # value should be a DirstateItem
138 138
139 139 # py2,3 compat forward
140 140 iteritems = items
141 141
142 142 def __getitem__(self, filename):
143 143 try:
144 144 gs = self.git.status_file(filename)
145 145 except KeyError:
146 146 return b'?'
147 147 return _STATUS_MAP[gs]
148 148
149 149 def __contains__(self, filename):
150 150 try:
151 151 gs = self.git.status_file(filename)
152 152 return _STATUS_MAP[gs] != b'?'
153 153 except KeyError:
154 154 return False
155 155
156 156 def status(self, match, subrepos, ignored, clean, unknown):
157 157 listclean = clean
158 158 # TODO handling of clean files - can we get that from git.status()?
159 159 modified, added, removed, deleted, unknown, ignored, clean = (
160 160 [],
161 161 [],
162 162 [],
163 163 [],
164 164 [],
165 165 [],
166 166 [],
167 167 )
168 168
169 169 try:
170 170 mtime_boundary = timestamp.get_fs_now(self._opener)
171 171 except OSError:
172 172 # In largefiles or readonly context
173 173 mtime_boundary = None
174 174
175 175 gstatus = self.git.status()
176 176 for path, status in gstatus.items():
177 177 path = pycompat.fsencode(path)
178 178 if not match(path):
179 179 continue
180 180 if status == pygit2.GIT_STATUS_IGNORED:
181 181 if path.endswith(b'/'):
182 182 continue
183 183 ignored.append(path)
184 184 elif status in (
185 185 pygit2.GIT_STATUS_WT_MODIFIED,
186 186 pygit2.GIT_STATUS_INDEX_MODIFIED,
187 187 pygit2.GIT_STATUS_WT_MODIFIED
188 188 | pygit2.GIT_STATUS_INDEX_MODIFIED,
189 189 ):
190 190 modified.append(path)
191 191 elif status == pygit2.GIT_STATUS_INDEX_NEW:
192 192 added.append(path)
193 193 elif status == pygit2.GIT_STATUS_WT_NEW:
194 194 unknown.append(path)
195 195 elif status == pygit2.GIT_STATUS_WT_DELETED:
196 196 deleted.append(path)
197 197 elif status == pygit2.GIT_STATUS_INDEX_DELETED:
198 198 removed.append(path)
199 199 else:
200 200 raise error.Abort(
201 201 b'unhandled case: status for %r is %r' % (path, status)
202 202 )
203 203
204 204 if listclean:
205 205 observed = set(
206 206 modified + added + removed + deleted + unknown + ignored
207 207 )
208 208 index = self.git.index
209 209 index.read()
210 210 for entry in index:
211 211 path = pycompat.fsencode(entry.path)
212 212 if not match(path):
213 213 continue
214 214 if path in observed:
215 215 continue # already in some other set
216 216 if path[-1] == b'/':
217 217 continue # directory
218 218 clean.append(path)
219 219
220 220 # TODO are we really always sure of status here?
221 221 return (
222 222 False,
223 223 scmutil.status(
224 224 modified, added, removed, deleted, unknown, ignored, clean
225 225 ),
226 226 mtime_boundary,
227 227 )
228 228
229 229 def flagfunc(self, buildfallback):
230 230 # TODO we can do better
231 231 return buildfallback()
232 232
233 233 def getcwd(self):
234 234 # TODO is this a good way to do this?
235 235 return os.path.dirname(
236 236 os.path.dirname(pycompat.fsencode(self.git.path))
237 237 )
238 238
239 239 def get_entry(self, path):
240 240 """return a DirstateItem for the associated path"""
241 241 entry = self._map.get(path)
242 242 if entry is None:
243 243 return DirstateItem()
244 244 return entry
245 245
246 def normalize(self, path):
246 def normalize(self, path, isknown=False, ignoremissing=False):
247 247 normed = util.normcase(path)
248 248 assert normed == path, b"TODO handling of case folding: %s != %s" % (
249 249 normed,
250 250 path,
251 251 )
252 252 return path
253 253
254 254 @property
255 255 def _checklink(self):
256 256 return util.checklink(os.path.dirname(pycompat.fsencode(self.git.path)))
257 257
258 258 def copies(self):
259 259 # TODO support copies?
260 260 return {}
261 261
262 262 # # TODO what the heck is this
263 263 _filecache = set()
264 264
265 @property
265 266 def is_changing_parents(self):
266 267 # TODO: we need to implement the context manager bits and
267 268 # correctly stage/revert index edits.
268 269 return False
269 270
271 @property
270 272 def is_changing_any(self):
271 273 # TODO: we need to implement the context manager bits and
272 274 # correctly stage/revert index edits.
273 275 return False
274 276
275 277 def write(self, tr):
276 278 # TODO: call parent change callbacks
277 279
278 280 if tr:
279 281
280 282 def writeinner(category):
281 283 self.git.index.write()
282 284
283 285 tr.addpending(b'gitdirstate', writeinner)
284 286 else:
285 287 self.git.index.write()
286 288
287 289 def pathto(self, f, cwd=None):
288 290 if cwd is None:
289 291 cwd = self.getcwd()
290 292 # TODO core dirstate does something about slashes here
291 293 assert isinstance(f, bytes)
292 294 r = util.pathto(self._root, cwd, f)
293 295 return r
294 296
295 297 def matches(self, match):
296 298 for x in self.git.index:
297 299 p = pycompat.fsencode(x.path)
298 300 if match(p):
299 301 yield p
300 302
301 303 def set_clean(self, f, parentfiledata):
302 304 """Mark a file normal and clean."""
303 305 # TODO: for now we just let libgit2 re-stat the file. We can
304 306 # clearly do better.
305 307
306 308 def set_possibly_dirty(self, f):
307 309 """Mark a file normal, but possibly dirty."""
308 310 # TODO: for now we just let libgit2 re-stat the file. We can
309 311 # clearly do better.
310 312
311 313 def walk(self, match, subrepos, unknown, ignored, full=True):
312 314 # TODO: we need to use .status() and not iterate the index,
313 315 # because the index doesn't force a re-walk and so `hg add` of
314 316 # a new file without an intervening call to status will
315 317 # silently do nothing.
316 318 r = {}
317 319 cwd = self.getcwd()
318 320 for path, status in self.git.status().items():
319 321 if path.startswith('.hg/'):
320 322 continue
321 323 path = pycompat.fsencode(path)
322 324 if not match(path):
323 325 continue
324 326 # TODO construct the stat info from the status object?
325 327 try:
326 328 s = os.stat(os.path.join(cwd, path))
327 329 except FileNotFoundError:
328 330 continue
329 331 r[path] = s
330 332 return r
331 333
332 334 def set_tracked(self, f, reset_copy=False):
333 335 # TODO: support copies and reset_copy=True
334 336 uf = pycompat.fsdecode(f)
335 337 if uf in self.git.index:
336 338 return False
337 339 index = self.git.index
338 340 index.read()
339 341 index.add(uf)
340 342 index.write()
341 343 return True
342 344
343 345 def add(self, f):
344 346 index = self.git.index
345 347 index.read()
346 348 index.add(pycompat.fsdecode(f))
347 349 index.write()
348 350
349 351 def drop(self, f):
350 352 index = self.git.index
351 353 index.read()
352 354 fs = pycompat.fsdecode(f)
353 355 if fs in index:
354 356 index.remove(fs)
355 357 index.write()
356 358
357 359 def set_untracked(self, f):
358 360 index = self.git.index
359 361 index.read()
360 362 fs = pycompat.fsdecode(f)
361 363 if fs in index:
362 364 index.remove(fs)
363 365 index.write()
364 366 return True
365 367 return False
366 368
367 369 def remove(self, f):
368 370 index = self.git.index
369 371 index.read()
370 372 index.remove(pycompat.fsdecode(f))
371 373 index.write()
372 374
373 375 def copied(self, path):
374 376 # TODO: track copies?
375 377 return None
376 378
377 379 def prefetch_parents(self):
378 380 # TODO
379 381 pass
380 382
381 383 def update_file(self, *args, **kwargs):
382 384 # TODO
383 385 pass
384 386
385 387 @contextlib.contextmanager
386 388 def changing_parents(self, repo):
387 389 # TODO: track this maybe?
388 390 yield
389 391
390 392 def addparentchangecallback(self, category, callback):
391 393 # TODO: should this be added to the dirstate interface?
392 394 self._plchangecallbacks[category] = callback
393 395
394 396 def setbranch(self, branch, transaction):
395 397 raise error.Abort(
396 398 b'git repos do not support branches. try using bookmarks'
397 399 )
General Comments 0
You need to be logged in to leave comments. Login now