##// END OF EJS Templates
largefile: use the proper "mtime boundary" logic during fixup...
largefile: use the proper "mtime boundary" logic during fixup This will prevent ambiguous cache entry to be used in racy situation. This fix flakiness in test and some real live misbehavior. Differential Revision: https://phab.mercurial-scm.org/D11800

File last commit:

r49208:080151f1 default
r49225:c0d88407 default
Show More
dirstate.py
369 lines | 10.6 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: replace nullid and friends with nodeconstants class...
r47771 from mercurial.node import sha1nodeconstants
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 = {}
Augie Fackler
git: restore basic functionality (issue6545)...
r48571 # TODO: context.poststatusfixup is bad and uses this attribute
self._dirty = False
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
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: replace nullid and friends with nodeconstants class...
r47771 return sha1nodeconstants.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: replace nullid and friends with nodeconstants class...
r47771 return sha1nodeconstants.nullid
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
Joerg Sonnenberger
node: replace nullid and friends with nodeconstants class...
r47771 def setparents(self, p1, p2=None):
if p2 is None:
p2 = sha1nodeconstants.nullid
assert p2 == sha1nodeconstants.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: replace nullid and friends with nodeconstants class...
r47771 return self.p1(), sha1nodeconstants.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:
dirstate-item: rename the class to DirstateItem...
r48328 yield ie.path, None # value should be a DirstateItem
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
# 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
dirstate: make it mandatory to provide parentfiledata in `set_clean`...
r49208 def set_clean(self, f, parentfiledata):
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 """Mark a file normal and clean."""
# TODO: for now we just let libgit2 re-stat the file. We can
# clearly do better.
Augie Fackler
git: restore basic functionality (issue6545)...
r48571 def set_possibly_dirty(self, f):
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 """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
Augie Fackler
git: restore basic functionality (issue6545)...
r48571 def set_tracked(self, f):
uf = pycompat.fsdecode(f)
if uf in self.git.index:
return False
index = self.git.index
index.read()
index.add(uf)
index.write()
return True
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 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
Augie Fackler
git: restore basic functionality (issue6545)...
r48571 def set_untracked(self, f):
index = self.git.index
index.read()
fs = pycompat.fsdecode(f)
if fs in index:
index.remove(fs)
index.write()
return True
return False
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: restore basic functionality (issue6545)...
r48571 def update_file(self, *args, **kwargs):
# 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'
)