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