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