##// END OF EJS Templates
git: ensure all dirstate state values are bytes...
git: ensure all dirstate state values are bytes I'm not sure how this particular git status occurs, but after the fallout of issue 6510 and getting into the issue 6511 state where `git status` shows the files as modified in both the "to be committed" and "not staged" lists, `hg diff` was crashing in `workingctx.__contains__()`. Differential Revision: https://phab.mercurial-scm.org/D10532

File last commit:

r47828:9cea55ca stable
r47828:9cea55ca stable
Show More
dirstate.py
341 lines | 9.8 KiB | text/x-python | PythonLexer
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 from __future__ import absolute_import
import contextlib
import errno
import os
Joerg Sonnenberger
node: import symbols explicitly...
r46729 from mercurial.node import nullid
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 from mercurial import (
error,
extensions,
match as matchmod,
pycompat,
scmutil,
util,
)
from mercurial.interfaces import (
dirstate as intdirstate,
util as interfaceutil,
)
from . import gitutil
Martin von Zweigbergk
git: don't fail import when pygit2 is not install...
r44968 pygit2 = gitutil.get_pygit2()
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
def readpatternfile(orig, filepath, warn, sourceinfo=False):
if not (b'info/exclude' in filepath or filepath.endswith(b'.gitignore')):
return orig(filepath, warn, sourceinfo=False)
result = []
warnings = []
with open(filepath, b'rb') as fp:
for l in fp:
l = l.strip()
if not l or l.startswith(b'#'):
continue
if l.startswith(b'!'):
warnings.append(b'unsupported ignore pattern %s' % l)
continue
if l.startswith(b'/'):
result.append(b'rootglob:' + l[1:])
else:
result.append(b'relglob:' + l)
return result, warnings
extensions.wrapfunction(matchmod, b'readpatternfile', readpatternfile)
Martin von Zweigbergk
git: don't fail import when pygit2 is not install...
r44968 _STATUS_MAP = {}
if pygit2:
_STATUS_MAP = {
pygit2.GIT_STATUS_CONFLICTED: b'm',
pygit2.GIT_STATUS_CURRENT: b'n',
pygit2.GIT_STATUS_IGNORED: b'?',
pygit2.GIT_STATUS_INDEX_DELETED: b'r',
pygit2.GIT_STATUS_INDEX_MODIFIED: b'n',
pygit2.GIT_STATUS_INDEX_NEW: b'a',
pygit2.GIT_STATUS_INDEX_RENAMED: b'a',
pygit2.GIT_STATUS_INDEX_TYPECHANGE: b'n',
pygit2.GIT_STATUS_WT_DELETED: b'r',
pygit2.GIT_STATUS_WT_MODIFIED: b'n',
pygit2.GIT_STATUS_WT_NEW: b'?',
pygit2.GIT_STATUS_WT_RENAMED: b'a',
pygit2.GIT_STATUS_WT_TYPECHANGE: b'n',
pygit2.GIT_STATUS_WT_UNREADABLE: b'?',
Matt Harbison
git: ensure all dirstate state values are bytes...
r47828 pygit2.GIT_STATUS_INDEX_MODIFIED | pygit2.GIT_STATUS_WT_MODIFIED: b'm',
Martin von Zweigbergk
git: don't fail import when pygit2 is not install...
r44968 }
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
@interfaceutil.implementer(intdirstate.idirstate)
class gitdirstate(object):
def __init__(self, ui, root, gitrepo):
self._ui = ui
self._root = os.path.dirname(root)
self.git = gitrepo
self._plchangecallbacks = {}
def p1(self):
Augie Fackler
git: correctly handle p1() on dirstate when underlying git repo is empty...
r44976 try:
return self.git.head.peel().id.raw
except pygit2.GitError:
# Typically happens when peeling HEAD fails, as in an
# empty repository.
Joerg Sonnenberger
node: import symbols explicitly...
r46729 return nullid
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
def p2(self):
# TODO: MERGE_HEAD? something like that, right?
Joerg Sonnenberger
node: import symbols explicitly...
r46729 return nullid
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
Joerg Sonnenberger
node: import symbols explicitly...
r46729 def setparents(self, p1, p2=nullid):
assert p2 == nullid, b'TODO merging support'
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 self.git.head.set_target(gitutil.togitnode(p1))
@util.propertycache
def identity(self):
return util.filestat.frompath(
os.path.join(self._root, b'.git', b'index')
)
def branch(self):
return b'default'
def parents(self):
# TODO how on earth do we find p2 if a merge is in flight?
Joerg Sonnenberger
node: import symbols explicitly...
r46729 return self.p1(), nullid
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
def __iter__(self):
return (pycompat.fsencode(f.path) for f in self.git.index)
def items(self):
for ie in self.git.index:
yield ie.path, None # value should be a dirstatetuple
# py2,3 compat forward
iteritems = items
def __getitem__(self, filename):
try:
gs = self.git.status_file(filename)
except KeyError:
return b'?'
return _STATUS_MAP[gs]
def __contains__(self, filename):
try:
gs = self.git.status_file(filename)
return _STATUS_MAP[gs] != b'?'
except KeyError:
return False
def status(self, match, subrepos, ignored, clean, unknown):
Pulkit Goyal
git: remove unrequired assignment of listignored and listunknown...
r46001 listclean = clean
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 # TODO handling of clean files - can we get that from git.status()?
modified, added, removed, deleted, unknown, ignored, clean = (
[],
[],
[],
[],
[],
[],
[],
)
gstatus = self.git.status()
for path, status in gstatus.items():
path = pycompat.fsencode(path)
Augie Fackler
git: make dirstate status() respect matcher...
r45990 if not match(path):
continue
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 if status == pygit2.GIT_STATUS_IGNORED:
if path.endswith(b'/'):
continue
ignored.append(path)
elif status in (
pygit2.GIT_STATUS_WT_MODIFIED,
pygit2.GIT_STATUS_INDEX_MODIFIED,
pygit2.GIT_STATUS_WT_MODIFIED
| pygit2.GIT_STATUS_INDEX_MODIFIED,
):
modified.append(path)
elif status == pygit2.GIT_STATUS_INDEX_NEW:
added.append(path)
elif status == pygit2.GIT_STATUS_WT_NEW:
unknown.append(path)
elif status == pygit2.GIT_STATUS_WT_DELETED:
deleted.append(path)
elif status == pygit2.GIT_STATUS_INDEX_DELETED:
removed.append(path)
else:
raise error.Abort(
b'unhandled case: status for %r is %r' % (path, status)
)
Augie Fackler
git: make dirstate actually support listclean parameter...
r45991 if listclean:
observed = set(
modified + added + removed + deleted + unknown + ignored
)
index = self.git.index
index.read()
for entry in index:
path = pycompat.fsencode(entry.path)
if not match(path):
continue
if path in observed:
continue # already in some other set
if path[-1] == b'/':
continue # directory
clean.append(path)
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 # TODO are we really always sure of status here?
return (
False,
scmutil.status(
modified, added, removed, deleted, unknown, ignored, clean
),
)
def flagfunc(self, buildfallback):
# TODO we can do better
return buildfallback()
def getcwd(self):
# TODO is this a good way to do this?
return os.path.dirname(
os.path.dirname(pycompat.fsencode(self.git.path))
)
def normalize(self, path):
normed = util.normcase(path)
assert normed == path, b"TODO handling of case folding: %s != %s" % (
normed,
path,
)
return path
@property
def _checklink(self):
return util.checklink(os.path.dirname(pycompat.fsencode(self.git.path)))
def copies(self):
# TODO support copies?
return {}
# # TODO what the heck is this
_filecache = set()
def pendingparentchange(self):
# TODO: we need to implement the context manager bits and
# correctly stage/revert index edits.
return False
def write(self, tr):
# TODO: call parent change callbacks
if tr:
def writeinner(category):
self.git.index.write()
tr.addpending(b'gitdirstate', writeinner)
else:
self.git.index.write()
def pathto(self, f, cwd=None):
if cwd is None:
cwd = self.getcwd()
# TODO core dirstate does something about slashes here
assert isinstance(f, bytes)
r = util.pathto(self._root, cwd, f)
return r
def matches(self, match):
for x in self.git.index:
p = pycompat.fsencode(x.path)
if match(p):
yield p
def normal(self, f, parentfiledata=None):
"""Mark a file normal and clean."""
# TODO: for now we just let libgit2 re-stat the file. We can
# clearly do better.
def normallookup(self, f):
"""Mark a file normal, but possibly dirty."""
# TODO: for now we just let libgit2 re-stat the file. We can
# clearly do better.
def walk(self, match, subrepos, unknown, ignored, full=True):
# TODO: we need to use .status() and not iterate the index,
# because the index doesn't force a re-walk and so `hg add` of
# a new file without an intervening call to status will
# silently do nothing.
r = {}
cwd = self.getcwd()
for path, status in self.git.status().items():
if path.startswith('.hg/'):
continue
path = pycompat.fsencode(path)
if not match(path):
continue
# TODO construct the stat info from the status object?
try:
s = os.stat(os.path.join(cwd, path))
except OSError as e:
if e.errno != errno.ENOENT:
raise
continue
r[path] = s
return r
def savebackup(self, tr, backupname):
# TODO: figure out a strategy for saving index backups.
pass
def restorebackup(self, tr, backupname):
# TODO: figure out a strategy for saving index backups.
pass
def add(self, f):
Augie Fackler
git: fix up dirstate use of index...
r45989 index = self.git.index
index.read()
index.add(pycompat.fsdecode(f))
index.write()
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
def drop(self, f):
Augie Fackler
git: fix up dirstate use of index...
r45989 index = self.git.index
index.read()
Augie Fackler
git: fix index handling of removed files during commit (issue6398)...
r45992 fs = pycompat.fsdecode(f)
if fs in index:
index.remove(fs)
index.write()
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
def remove(self, f):
Augie Fackler
git: fix up dirstate use of index...
r45989 index = self.git.index
index.read()
index.remove(pycompat.fsdecode(f))
index.write()
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
def copied(self, path):
# TODO: track copies?
return None
Josef 'Jeff' Sipek
git: implement stub prefetch_parents dirstate method...
r45446 def prefetch_parents(self):
# TODO
pass
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 @contextlib.contextmanager
def parentchange(self):
# TODO: track this maybe?
yield
def addparentchangecallback(self, category, callback):
# TODO: should this be added to the dirstate interface?
self._plchangecallbacks[category] = callback
def clearbackup(self, tr, backupname):
# TODO
pass
Josef 'Jeff' Sipek
git: abort when attempting to set a branch...
r45112
def setbranch(self, branch):
raise error.Abort(
b'git repos do not support branches. try using bookmarks'
)