##// END OF EJS Templates
wrapfunction: use sysstr instead of bytes as argument in the "git" extension...
marmoute -
r51672:39eb3aab default
parent child Browse files
Show More
@@ -1,352 +1,352 b''
1 1 """grant Mercurial the ability to operate on Git repositories. (EXPERIMENTAL)
2 2
3 3 This is currently super experimental. It probably will consume your
4 4 firstborn a la Rumpelstiltskin, etc.
5 5 """
6 6
7 7
8 8 import os
9 9
10 10 from mercurial.i18n import _
11 11
12 12 from mercurial import (
13 13 commands,
14 14 error,
15 15 extensions,
16 16 localrepo,
17 17 pycompat,
18 18 registrar,
19 19 requirements as requirementsmod,
20 20 scmutil,
21 21 store,
22 22 util,
23 23 )
24 24
25 25 from . import (
26 26 dirstate,
27 27 gitlog,
28 28 gitutil,
29 29 index,
30 30 )
31 31
32 32 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
33 33 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
34 34 # be specifying the version(s) of Mercurial they are tested with, or
35 35 # leave the attribute unspecified.
36 36 testedwith = b'ships-with-hg-core'
37 37
38 38 configtable = {}
39 39 configitem = registrar.configitem(configtable)
40 40 # git.log-index-cache-miss: internal knob for testing
41 41 configitem(
42 42 b"git",
43 43 b"log-index-cache-miss",
44 44 default=False,
45 45 )
46 46
47 47 getversion = gitutil.pygit2_version
48 48
49 49
50 50 # TODO: extract an interface for this in core
51 51 class gitstore: # store.basicstore):
52 52 def __init__(self, path, vfstype):
53 53 self.vfs = vfstype(path)
54 54 self.opener = self.vfs
55 55 self.path = self.vfs.base
56 56 self.createmode = store._calcmode(self.vfs)
57 57 # above lines should go away in favor of:
58 58 # super(gitstore, self).__init__(path, vfstype)
59 59
60 60 self.git = gitutil.get_pygit2().Repository(
61 61 os.path.normpath(os.path.join(path, b'..', b'.git'))
62 62 )
63 63 self._progress_factory = lambda *args, **kwargs: None
64 64 self._logfn = lambda x: None
65 65
66 66 @util.propertycache
67 67 def _db(self):
68 68 # We lazy-create the database because we want to thread a
69 69 # progress callback down to the indexing process if it's
70 70 # required, and we don't have a ui handle in makestore().
71 71 return index.get_index(self.git, self._logfn, self._progress_factory)
72 72
73 73 def join(self, f):
74 74 """Fake store.join method for git repositories.
75 75
76 76 For the most part, store.join is used for @storecache
77 77 decorators to invalidate caches when various files
78 78 change. We'll map the ones we care about, and ignore the rest.
79 79 """
80 80 if f in (b'00changelog.i', b'00manifest.i'):
81 81 # This is close enough: in order for the changelog cache
82 82 # to be invalidated, HEAD will have to change.
83 83 return os.path.join(self.path, b'HEAD')
84 84 elif f == b'lock':
85 85 # TODO: we probably want to map this to a git lock, I
86 86 # suspect index.lock. We should figure out what the
87 87 # most-alike file is in git-land. For now we're risking
88 88 # bad concurrency errors if another git client is used.
89 89 return os.path.join(self.path, b'hgit-bogus-lock')
90 90 elif f in (b'obsstore', b'phaseroots', b'narrowspec', b'bookmarks'):
91 91 return os.path.join(self.path, b'..', b'.hg', f)
92 92 raise NotImplementedError(b'Need to pick file for %s.' % f)
93 93
94 94 def changelog(self, trypending, concurrencychecker):
95 95 # TODO we don't have a plan for trypending in hg's git support yet
96 96 return gitlog.changelog(self.git, self._db)
97 97
98 98 def manifestlog(self, repo, storenarrowmatch):
99 99 # TODO handle storenarrowmatch and figure out if we need the repo arg
100 100 return gitlog.manifestlog(self.git, self._db)
101 101
102 102 def invalidatecaches(self):
103 103 pass
104 104
105 105 def write(self, tr=None):
106 106 # normally this handles things like fncache writes, which we don't have
107 107 pass
108 108
109 109
110 110 def _makestore(orig, requirements, storebasepath, vfstype):
111 111 if b'git' in requirements:
112 112 if not os.path.exists(os.path.join(storebasepath, b'..', b'.git')):
113 113 raise error.Abort(
114 114 _(
115 115 b'repository specified git format in '
116 116 b'.hg/requires but has no .git directory'
117 117 )
118 118 )
119 119 # Check for presence of pygit2 only here. The assumption is that we'll
120 120 # run this code iff we'll later need pygit2.
121 121 if gitutil.get_pygit2() is None:
122 122 raise error.Abort(
123 123 _(
124 124 b'the git extension requires the Python '
125 125 b'pygit2 library to be installed'
126 126 )
127 127 )
128 128
129 129 return gitstore(storebasepath, vfstype)
130 130 return orig(requirements, storebasepath, vfstype)
131 131
132 132
133 133 class gitfilestorage:
134 134 def file(self, path):
135 135 if path[0:1] == b'/':
136 136 path = path[1:]
137 137 return gitlog.filelog(self.store.git, self.store._db, path)
138 138
139 139
140 140 def _makefilestorage(orig, requirements, features, **kwargs):
141 141 store = kwargs['store']
142 142 if isinstance(store, gitstore):
143 143 return gitfilestorage
144 144 return orig(requirements, features, **kwargs)
145 145
146 146
147 147 def _setupdothg(ui, path):
148 148 dothg = os.path.join(path, b'.hg')
149 149 if os.path.exists(dothg):
150 150 ui.warn(_(b'git repo already initialized for hg\n'))
151 151 else:
152 152 os.mkdir(os.path.join(path, b'.hg'))
153 153 # TODO is it ok to extend .git/info/exclude like this?
154 154 with open(
155 155 os.path.join(path, b'.git', b'info', b'exclude'), 'ab'
156 156 ) as exclude:
157 157 exclude.write(b'\n.hg\n')
158 158 with open(os.path.join(dothg, b'requires'), 'wb') as f:
159 159 f.write(b'git\n')
160 160
161 161
162 162 _BMS_PREFIX = 'refs/heads/'
163 163
164 164
165 165 class gitbmstore:
166 166 def __init__(self, gitrepo):
167 167 self.gitrepo = gitrepo
168 168 self._aclean = True
169 169 self._active = gitrepo.references['HEAD'] # git head, not mark
170 170
171 171 def __contains__(self, name):
172 172 return (
173 173 _BMS_PREFIX + pycompat.fsdecode(name)
174 174 ) in self.gitrepo.references
175 175
176 176 def __iter__(self):
177 177 for r in self.gitrepo.listall_references():
178 178 if r.startswith(_BMS_PREFIX):
179 179 yield pycompat.fsencode(r[len(_BMS_PREFIX) :])
180 180
181 181 def __getitem__(self, k):
182 182 return (
183 183 self.gitrepo.references[_BMS_PREFIX + pycompat.fsdecode(k)]
184 184 .peel()
185 185 .id.raw
186 186 )
187 187
188 188 def get(self, k, default=None):
189 189 try:
190 190 if k in self:
191 191 return self[k]
192 192 return default
193 193 except gitutil.get_pygit2().InvalidSpecError:
194 194 return default
195 195
196 196 @property
197 197 def active(self):
198 198 h = self.gitrepo.references['HEAD']
199 199 if not isinstance(h.target, str) or not h.target.startswith(
200 200 _BMS_PREFIX
201 201 ):
202 202 return None
203 203 return pycompat.fsencode(h.target[len(_BMS_PREFIX) :])
204 204
205 205 @active.setter
206 206 def active(self, mark):
207 207 githead = None
208 208 if mark is not None:
209 209 githead = _BMS_PREFIX + pycompat.fsdecode(mark)
210 210 if githead is not None and githead not in self.gitrepo.references:
211 211 raise AssertionError(b'bookmark %s does not exist!' % mark)
212 212
213 213 self._active = githead
214 214 self._aclean = False
215 215
216 216 def _writeactive(self):
217 217 if self._aclean:
218 218 return
219 219 self.gitrepo.references.create('HEAD', self._active, True)
220 220 self._aclean = True
221 221
222 222 def names(self, node):
223 223 r = []
224 224 for ref in self.gitrepo.listall_references():
225 225 if not ref.startswith(_BMS_PREFIX):
226 226 continue
227 227 if self.gitrepo.references[ref].peel().id.raw != node:
228 228 continue
229 229 r.append(pycompat.fsencode(ref[len(_BMS_PREFIX) :]))
230 230 return r
231 231
232 232 # Cleanup opportunity: this is *identical* to core's bookmarks store.
233 233 def expandname(self, bname):
234 234 if bname == b'.':
235 235 if self.active:
236 236 return self.active
237 237 raise error.RepoLookupError(_(b"no active bookmark"))
238 238 return bname
239 239
240 240 def applychanges(self, repo, tr, changes):
241 241 """Apply a list of changes to bookmarks"""
242 242 # TODO: this should respect transactions, but that's going to
243 243 # require enlarging the gitbmstore to know how to do in-memory
244 244 # temporary writes and read those back prior to transaction
245 245 # finalization.
246 246 for name, node in changes:
247 247 if node is None:
248 248 self.gitrepo.references.delete(
249 249 _BMS_PREFIX + pycompat.fsdecode(name)
250 250 )
251 251 else:
252 252 self.gitrepo.references.create(
253 253 _BMS_PREFIX + pycompat.fsdecode(name),
254 254 gitutil.togitnode(node),
255 255 force=True,
256 256 )
257 257
258 258 def checkconflict(self, mark, force=False, target=None):
259 259 githead = _BMS_PREFIX + pycompat.fsdecode(mark)
260 260 cur = self.gitrepo.references['HEAD']
261 261 if githead in self.gitrepo.references and not force:
262 262 if target:
263 263 if self.gitrepo.references[githead] == target and target == cur:
264 264 # re-activating a bookmark
265 265 return []
266 266 # moving a bookmark - forward?
267 267 raise NotImplementedError
268 268 raise error.Abort(
269 269 _(b"bookmark '%s' already exists (use -f to force)") % mark
270 270 )
271 271 if len(mark) > 3 and not force:
272 272 try:
273 273 shadowhash = scmutil.isrevsymbol(self._repo, mark)
274 274 except error.LookupError: # ambiguous identifier
275 275 shadowhash = False
276 276 if shadowhash:
277 277 self._repo.ui.warn(
278 278 _(
279 279 b"bookmark %s matches a changeset hash\n"
280 280 b"(did you leave a -r out of an 'hg bookmark' "
281 281 b"command?)\n"
282 282 )
283 283 % mark
284 284 )
285 285 return []
286 286
287 287
288 288 def init(orig, ui, dest=b'.', **opts):
289 289 if opts.get('git', False):
290 290 path = util.abspath(dest)
291 291 # TODO: walk up looking for the git repo
292 292 _setupdothg(ui, path)
293 293 return 0
294 294 return orig(ui, dest=dest, **opts)
295 295
296 296
297 297 def reposetup(ui, repo):
298 298 if repo.local() and isinstance(repo.store, gitstore):
299 299 orig = repo.__class__
300 300 repo.store._progress_factory = repo.ui.makeprogress
301 301 if ui.configbool(b'git', b'log-index-cache-miss'):
302 302 repo.store._logfn = repo.ui.warn
303 303
304 304 class gitlocalrepo(orig):
305 305 def _makedirstate(self):
306 306 v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
307 307 use_dirstate_v2 = v2_req in self.requirements
308 308
309 309 # TODO narrow support here
310 310 return dirstate.gitdirstate(
311 311 self.ui,
312 312 self.vfs,
313 313 self.store.git,
314 314 use_dirstate_v2,
315 315 )
316 316
317 317 def commit(self, *args, **kwargs):
318 318 ret = orig.commit(self, *args, **kwargs)
319 319 if ret is None:
320 320 # there was nothing to commit, so we should skip
321 321 # the index fixup logic we'd otherwise do.
322 322 return None
323 323 tid = self.store.git[gitutil.togitnode(ret)].tree.id
324 324 # DANGER! This will flush any writes staged to the
325 325 # index in Git, but we're sidestepping the index in a
326 326 # way that confuses git when we commit. Alas.
327 327 self.store.git.index.read_tree(tid)
328 328 self.store.git.index.write()
329 329 return ret
330 330
331 331 @property
332 332 def _bookmarks(self):
333 333 return gitbmstore(self.store.git)
334 334
335 335 repo.__class__ = gitlocalrepo
336 336 return repo
337 337
338 338
339 339 def _featuresetup(ui, supported):
340 340 # don't die on seeing a repo with the git requirement
341 341 supported |= {b'git'}
342 342
343 343
344 344 def extsetup(ui):
345 extensions.wrapfunction(localrepo, b'makestore', _makestore)
346 extensions.wrapfunction(localrepo, b'makefilestorage', _makefilestorage)
345 extensions.wrapfunction(localrepo, 'makestore', _makestore)
346 extensions.wrapfunction(localrepo, 'makefilestorage', _makefilestorage)
347 347 # Inject --git flag for `hg init`
348 348 entry = extensions.wrapcommand(commands.table, b'init', init)
349 349 entry[1].extend(
350 350 [(b'', b'git', None, b'setup up a git repository instead of hg')]
351 351 )
352 352 localrepo.featuresetupfuncs.add(_featuresetup)
@@ -1,395 +1,395 b''
1 1 import contextlib
2 2 import os
3 3
4 4 from mercurial.node import sha1nodeconstants
5 5 from mercurial import (
6 6 dirstatemap,
7 7 error,
8 8 extensions,
9 9 match as matchmod,
10 10 pycompat,
11 11 scmutil,
12 12 util,
13 13 )
14 14 from mercurial.dirstateutils import (
15 15 timestamp,
16 16 )
17 17 from mercurial.interfaces import (
18 18 dirstate as intdirstate,
19 19 util as interfaceutil,
20 20 )
21 21
22 22 from . import gitutil
23 23
24 24
25 25 DirstateItem = dirstatemap.DirstateItem
26 26 propertycache = util.propertycache
27 27 pygit2 = gitutil.get_pygit2()
28 28
29 29
30 30 def readpatternfile(orig, filepath, warn, sourceinfo=False):
31 31 if not (b'info/exclude' in filepath or filepath.endswith(b'.gitignore')):
32 32 return orig(filepath, warn, sourceinfo=False)
33 33 result = []
34 34 warnings = []
35 35 with open(filepath, 'rb') as fp:
36 36 for l in fp:
37 37 l = l.strip()
38 38 if not l or l.startswith(b'#'):
39 39 continue
40 40 if l.startswith(b'!'):
41 41 warnings.append(b'unsupported ignore pattern %s' % l)
42 42 continue
43 43 if l.startswith(b'/'):
44 44 result.append(b'rootglob:' + l[1:])
45 45 else:
46 46 result.append(b'relglob:' + l)
47 47 return result, warnings
48 48
49 49
50 extensions.wrapfunction(matchmod, b'readpatternfile', readpatternfile)
50 extensions.wrapfunction(matchmod, 'readpatternfile', readpatternfile)
51 51
52 52
53 53 _STATUS_MAP = {}
54 54 if pygit2:
55 55 _STATUS_MAP = {
56 56 pygit2.GIT_STATUS_CONFLICTED: b'm',
57 57 pygit2.GIT_STATUS_CURRENT: b'n',
58 58 pygit2.GIT_STATUS_IGNORED: b'?',
59 59 pygit2.GIT_STATUS_INDEX_DELETED: b'r',
60 60 pygit2.GIT_STATUS_INDEX_MODIFIED: b'n',
61 61 pygit2.GIT_STATUS_INDEX_NEW: b'a',
62 62 pygit2.GIT_STATUS_INDEX_RENAMED: b'a',
63 63 pygit2.GIT_STATUS_INDEX_TYPECHANGE: b'n',
64 64 pygit2.GIT_STATUS_WT_DELETED: b'r',
65 65 pygit2.GIT_STATUS_WT_MODIFIED: b'n',
66 66 pygit2.GIT_STATUS_WT_NEW: b'?',
67 67 pygit2.GIT_STATUS_WT_RENAMED: b'a',
68 68 pygit2.GIT_STATUS_WT_TYPECHANGE: b'n',
69 69 pygit2.GIT_STATUS_WT_UNREADABLE: b'?',
70 70 pygit2.GIT_STATUS_INDEX_MODIFIED | pygit2.GIT_STATUS_WT_MODIFIED: b'm',
71 71 }
72 72
73 73
74 74 @interfaceutil.implementer(intdirstate.idirstate)
75 75 class gitdirstate:
76 76 def __init__(self, ui, vfs, gitrepo, use_dirstate_v2):
77 77 self._ui = ui
78 78 self._root = os.path.dirname(vfs.base)
79 79 self._opener = vfs
80 80 self.git = gitrepo
81 81 self._plchangecallbacks = {}
82 82 # TODO: context.poststatusfixup is bad and uses this attribute
83 83 self._dirty = False
84 84 self._mapcls = dirstatemap.dirstatemap
85 85 self._use_dirstate_v2 = use_dirstate_v2
86 86
87 87 @propertycache
88 88 def _map(self):
89 89 """Return the dirstate contents (see documentation for dirstatemap)."""
90 90 self._map = self._mapcls(
91 91 self._ui,
92 92 self._opener,
93 93 self._root,
94 94 sha1nodeconstants,
95 95 self._use_dirstate_v2,
96 96 )
97 97 return self._map
98 98
99 99 def p1(self):
100 100 try:
101 101 return self.git.head.peel().id.raw
102 102 except pygit2.GitError:
103 103 # Typically happens when peeling HEAD fails, as in an
104 104 # empty repository.
105 105 return sha1nodeconstants.nullid
106 106
107 107 def p2(self):
108 108 # TODO: MERGE_HEAD? something like that, right?
109 109 return sha1nodeconstants.nullid
110 110
111 111 def setparents(self, p1, p2=None):
112 112 if p2 is None:
113 113 p2 = sha1nodeconstants.nullid
114 114 assert p2 == sha1nodeconstants.nullid, b'TODO merging support'
115 115 self.git.head.set_target(gitutil.togitnode(p1))
116 116
117 117 @util.propertycache
118 118 def identity(self):
119 119 return util.filestat.frompath(
120 120 os.path.join(self._root, b'.git', b'index')
121 121 )
122 122
123 123 def branch(self):
124 124 return b'default'
125 125
126 126 def parents(self):
127 127 # TODO how on earth do we find p2 if a merge is in flight?
128 128 return self.p1(), sha1nodeconstants.nullid
129 129
130 130 def __iter__(self):
131 131 return (pycompat.fsencode(f.path) for f in self.git.index)
132 132
133 133 def items(self):
134 134 for ie in self.git.index:
135 135 yield ie.path, None # value should be a DirstateItem
136 136
137 137 # py2,3 compat forward
138 138 iteritems = items
139 139
140 140 def __getitem__(self, filename):
141 141 try:
142 142 gs = self.git.status_file(filename)
143 143 except KeyError:
144 144 return b'?'
145 145 return _STATUS_MAP[gs]
146 146
147 147 def __contains__(self, filename):
148 148 try:
149 149 gs = self.git.status_file(filename)
150 150 return _STATUS_MAP[gs] != b'?'
151 151 except KeyError:
152 152 return False
153 153
154 154 def status(self, match, subrepos, ignored, clean, unknown):
155 155 listclean = clean
156 156 # TODO handling of clean files - can we get that from git.status()?
157 157 modified, added, removed, deleted, unknown, ignored, clean = (
158 158 [],
159 159 [],
160 160 [],
161 161 [],
162 162 [],
163 163 [],
164 164 [],
165 165 )
166 166
167 167 try:
168 168 mtime_boundary = timestamp.get_fs_now(self._opener)
169 169 except OSError:
170 170 # In largefiles or readonly context
171 171 mtime_boundary = None
172 172
173 173 gstatus = self.git.status()
174 174 for path, status in gstatus.items():
175 175 path = pycompat.fsencode(path)
176 176 if not match(path):
177 177 continue
178 178 if status == pygit2.GIT_STATUS_IGNORED:
179 179 if path.endswith(b'/'):
180 180 continue
181 181 ignored.append(path)
182 182 elif status in (
183 183 pygit2.GIT_STATUS_WT_MODIFIED,
184 184 pygit2.GIT_STATUS_INDEX_MODIFIED,
185 185 pygit2.GIT_STATUS_WT_MODIFIED
186 186 | pygit2.GIT_STATUS_INDEX_MODIFIED,
187 187 ):
188 188 modified.append(path)
189 189 elif status == pygit2.GIT_STATUS_INDEX_NEW:
190 190 added.append(path)
191 191 elif status == pygit2.GIT_STATUS_WT_NEW:
192 192 unknown.append(path)
193 193 elif status == pygit2.GIT_STATUS_WT_DELETED:
194 194 deleted.append(path)
195 195 elif status == pygit2.GIT_STATUS_INDEX_DELETED:
196 196 removed.append(path)
197 197 else:
198 198 raise error.Abort(
199 199 b'unhandled case: status for %r is %r' % (path, status)
200 200 )
201 201
202 202 if listclean:
203 203 observed = set(
204 204 modified + added + removed + deleted + unknown + ignored
205 205 )
206 206 index = self.git.index
207 207 index.read()
208 208 for entry in index:
209 209 path = pycompat.fsencode(entry.path)
210 210 if not match(path):
211 211 continue
212 212 if path in observed:
213 213 continue # already in some other set
214 214 if path[-1] == b'/':
215 215 continue # directory
216 216 clean.append(path)
217 217
218 218 # TODO are we really always sure of status here?
219 219 return (
220 220 False,
221 221 scmutil.status(
222 222 modified, added, removed, deleted, unknown, ignored, clean
223 223 ),
224 224 mtime_boundary,
225 225 )
226 226
227 227 def flagfunc(self, buildfallback):
228 228 # TODO we can do better
229 229 return buildfallback()
230 230
231 231 def getcwd(self):
232 232 # TODO is this a good way to do this?
233 233 return os.path.dirname(
234 234 os.path.dirname(pycompat.fsencode(self.git.path))
235 235 )
236 236
237 237 def get_entry(self, path):
238 238 """return a DirstateItem for the associated path"""
239 239 entry = self._map.get(path)
240 240 if entry is None:
241 241 return DirstateItem()
242 242 return entry
243 243
244 244 def normalize(self, path):
245 245 normed = util.normcase(path)
246 246 assert normed == path, b"TODO handling of case folding: %s != %s" % (
247 247 normed,
248 248 path,
249 249 )
250 250 return path
251 251
252 252 @property
253 253 def _checklink(self):
254 254 return util.checklink(os.path.dirname(pycompat.fsencode(self.git.path)))
255 255
256 256 def copies(self):
257 257 # TODO support copies?
258 258 return {}
259 259
260 260 # # TODO what the heck is this
261 261 _filecache = set()
262 262
263 263 def is_changing_parents(self):
264 264 # TODO: we need to implement the context manager bits and
265 265 # correctly stage/revert index edits.
266 266 return False
267 267
268 268 def is_changing_any(self):
269 269 # TODO: we need to implement the context manager bits and
270 270 # correctly stage/revert index edits.
271 271 return False
272 272
273 273 def write(self, tr):
274 274 # TODO: call parent change callbacks
275 275
276 276 if tr:
277 277
278 278 def writeinner(category):
279 279 self.git.index.write()
280 280
281 281 tr.addpending(b'gitdirstate', writeinner)
282 282 else:
283 283 self.git.index.write()
284 284
285 285 def pathto(self, f, cwd=None):
286 286 if cwd is None:
287 287 cwd = self.getcwd()
288 288 # TODO core dirstate does something about slashes here
289 289 assert isinstance(f, bytes)
290 290 r = util.pathto(self._root, cwd, f)
291 291 return r
292 292
293 293 def matches(self, match):
294 294 for x in self.git.index:
295 295 p = pycompat.fsencode(x.path)
296 296 if match(p):
297 297 yield p
298 298
299 299 def set_clean(self, f, parentfiledata):
300 300 """Mark a file normal and clean."""
301 301 # TODO: for now we just let libgit2 re-stat the file. We can
302 302 # clearly do better.
303 303
304 304 def set_possibly_dirty(self, f):
305 305 """Mark a file normal, but possibly dirty."""
306 306 # TODO: for now we just let libgit2 re-stat the file. We can
307 307 # clearly do better.
308 308
309 309 def walk(self, match, subrepos, unknown, ignored, full=True):
310 310 # TODO: we need to use .status() and not iterate the index,
311 311 # because the index doesn't force a re-walk and so `hg add` of
312 312 # a new file without an intervening call to status will
313 313 # silently do nothing.
314 314 r = {}
315 315 cwd = self.getcwd()
316 316 for path, status in self.git.status().items():
317 317 if path.startswith('.hg/'):
318 318 continue
319 319 path = pycompat.fsencode(path)
320 320 if not match(path):
321 321 continue
322 322 # TODO construct the stat info from the status object?
323 323 try:
324 324 s = os.stat(os.path.join(cwd, path))
325 325 except FileNotFoundError:
326 326 continue
327 327 r[path] = s
328 328 return r
329 329
330 330 def set_tracked(self, f, reset_copy=False):
331 331 # TODO: support copies and reset_copy=True
332 332 uf = pycompat.fsdecode(f)
333 333 if uf in self.git.index:
334 334 return False
335 335 index = self.git.index
336 336 index.read()
337 337 index.add(uf)
338 338 index.write()
339 339 return True
340 340
341 341 def add(self, f):
342 342 index = self.git.index
343 343 index.read()
344 344 index.add(pycompat.fsdecode(f))
345 345 index.write()
346 346
347 347 def drop(self, f):
348 348 index = self.git.index
349 349 index.read()
350 350 fs = pycompat.fsdecode(f)
351 351 if fs in index:
352 352 index.remove(fs)
353 353 index.write()
354 354
355 355 def set_untracked(self, f):
356 356 index = self.git.index
357 357 index.read()
358 358 fs = pycompat.fsdecode(f)
359 359 if fs in index:
360 360 index.remove(fs)
361 361 index.write()
362 362 return True
363 363 return False
364 364
365 365 def remove(self, f):
366 366 index = self.git.index
367 367 index.read()
368 368 index.remove(pycompat.fsdecode(f))
369 369 index.write()
370 370
371 371 def copied(self, path):
372 372 # TODO: track copies?
373 373 return None
374 374
375 375 def prefetch_parents(self):
376 376 # TODO
377 377 pass
378 378
379 379 def update_file(self, *args, **kwargs):
380 380 # TODO
381 381 pass
382 382
383 383 @contextlib.contextmanager
384 384 def changing_parents(self, repo):
385 385 # TODO: track this maybe?
386 386 yield
387 387
388 388 def addparentchangecallback(self, category, callback):
389 389 # TODO: should this be added to the dirstate interface?
390 390 self._plchangecallbacks[category] = callback
391 391
392 392 def setbranch(self, branch, transaction=None):
393 393 raise error.Abort(
394 394 b'git repos do not support branches. try using bookmarks'
395 395 )
General Comments 0
You need to be logged in to leave comments. Login now