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