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