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