##// END OF EJS Templates
py3: replace `pycompat.xrange` by `range`
py3: replace `pycompat.xrange` by `range`

File last commit:

r49950:aa400bf6 default
r50179:d44e3c45 default
Show More
__init__.py
350 lines | 11.6 KiB | text/x-python | PythonLexer
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 """grant Mercurial the ability to operate on Git repositories. (EXPERIMENTAL)
This is currently super experimental. It probably will consume your
firstborn a la Rumpelstiltskin, etc.
"""
import os
from mercurial.i18n import _
from mercurial import (
commands,
error,
extensions,
localrepo,
pycompat,
Augie Fackler
git: add debug logging when there's a mismatch in the cached heads list...
r45478 registrar,
Matt Harbison
git: adapt to some recent dirstate API changes...
r49968 requirements as requirementsmod,
Josef 'Jeff' Sipek
git: implement a basic checkconflict bookmark store method...
r45113 scmutil,
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 store,
util,
)
from . import (
dirstate,
gitlog,
gitutil,
index,
)
Matt Harbison
git: add the standard `testedwith` attribute...
r46561 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# be specifying the version(s) of Mercurial they are tested with, or
# leave the attribute unspecified.
testedwith = b'ships-with-hg-core'
Augie Fackler
git: add debug logging when there's a mismatch in the cached heads list...
r45478 configtable = {}
configitem = registrar.configitem(configtable)
# git.log-index-cache-miss: internal knob for testing
configitem(
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 b"git",
b"log-index-cache-miss",
default=False,
Augie Fackler
git: add debug logging when there's a mismatch in the cached heads list...
r45478 )
Matt Harbison
git: show the version of `pygit2` with verbose version output...
r46562 getversion = gitutil.pygit2_version
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 # TODO: extract an interface for this in core
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class gitstore: # store.basicstore):
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 def __init__(self, path, vfstype):
self.vfs = vfstype(path)
Augie Fackler
git: add opener attribute to gitstore...
r49371 self.opener = self.vfs
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 self.path = self.vfs.base
self.createmode = store._calcmode(self.vfs)
# above lines should go away in favor of:
# super(gitstore, self).__init__(path, vfstype)
Martin von Zweigbergk
git: don't fail import when pygit2 is not install...
r44968 self.git = gitutil.get_pygit2().Repository(
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 os.path.normpath(os.path.join(path, b'..', b'.git'))
)
self._progress_factory = lambda *args, **kwargs: None
Augie Fackler
git: add debug logging when there's a mismatch in the cached heads list...
r45478 self._logfn = lambda x: None
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
@util.propertycache
def _db(self):
# We lazy-create the database because we want to thread a
# progress callback down to the indexing process if it's
# required, and we don't have a ui handle in makestore().
Augie Fackler
git: add debug logging when there's a mismatch in the cached heads list...
r45478 return index.get_index(self.git, self._logfn, self._progress_factory)
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
def join(self, f):
"""Fake store.join method for git repositories.
For the most part, store.join is used for @storecache
decorators to invalidate caches when various files
change. We'll map the ones we care about, and ignore the rest.
"""
if f in (b'00changelog.i', b'00manifest.i'):
# This is close enough: in order for the changelog cache
# to be invalidated, HEAD will have to change.
return os.path.join(self.path, b'HEAD')
elif f == b'lock':
# TODO: we probably want to map this to a git lock, I
# suspect index.lock. We should figure out what the
# most-alike file is in git-land. For now we're risking
# bad concurrency errors if another git client is used.
return os.path.join(self.path, b'hgit-bogus-lock')
elif f in (b'obsstore', b'phaseroots', b'narrowspec', b'bookmarks'):
return os.path.join(self.path, b'..', b'.hg', f)
raise NotImplementedError(b'Need to pick file for %s.' % f)
Kyle Lippincott
revlog: add a mechanism to verify expected file position before appending...
r47349 def changelog(self, trypending, concurrencychecker):
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 # TODO we don't have a plan for trypending in hg's git support yet
return gitlog.changelog(self.git, self._db)
def manifestlog(self, repo, storenarrowmatch):
# TODO handle storenarrowmatch and figure out if we need the repo arg
return gitlog.manifestlog(self.git, self._db)
def invalidatecaches(self):
pass
def write(self, tr=None):
# normally this handles things like fncache writes, which we don't have
pass
def _makestore(orig, requirements, storebasepath, vfstype):
Augie Fackler
git: key off `git` in .hg/requires rather than separate file...
r44977 if b'git' in requirements:
if not os.path.exists(os.path.join(storebasepath, b'..', b'.git')):
raise error.Abort(
_(
b'repository specified git format in '
b'.hg/requires but has no .git directory'
)
Martin von Zweigbergk
git: don't fail import when pygit2 is not install...
r44968 )
Augie Fackler
git: key off `git` in .hg/requires rather than separate file...
r44977 # Check for presence of pygit2 only here. The assumption is that we'll
# run this code iff we'll later need pygit2.
if gitutil.get_pygit2() is None:
raise error.Abort(
_(
b'the git extension requires the Python '
b'pygit2 library to be installed'
)
)
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 return gitstore(storebasepath, vfstype)
return orig(requirements, storebasepath, vfstype)
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class gitfilestorage:
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 def file(self, path):
if path[0:1] == b'/':
path = path[1:]
return gitlog.filelog(self.store.git, self.store._db, path)
def _makefilestorage(orig, requirements, features, **kwargs):
store = kwargs['store']
if isinstance(store, gitstore):
return gitfilestorage
return orig(requirements, features, **kwargs)
def _setupdothg(ui, path):
dothg = os.path.join(path, b'.hg')
if os.path.exists(dothg):
ui.warn(_(b'git repo already initialized for hg\n'))
else:
os.mkdir(os.path.join(path, b'.hg'))
# TODO is it ok to extend .git/info/exclude like this?
with open(
os.path.join(path, b'.git', b'info', b'exclude'), 'ab'
) as exclude:
exclude.write(b'\n.hg\n')
Augie Fackler
git: key off `git` in .hg/requires rather than separate file...
r44977 with open(os.path.join(dothg, b'requires'), 'wb') as f:
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 f.write(b'git\n')
_BMS_PREFIX = 'refs/heads/'
Gregory Szorc
py3: use class X: instead of class X(object):...
r49801 class gitbmstore:
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 def __init__(self, gitrepo):
self.gitrepo = gitrepo
Josef 'Jeff' Sipek
git: implement basic bookmark activation...
r45114 self._aclean = True
self._active = gitrepo.references['HEAD'] # git head, not mark
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
def __contains__(self, name):
return (
_BMS_PREFIX + pycompat.fsdecode(name)
) in self.gitrepo.references
def __iter__(self):
for r in self.gitrepo.listall_references():
if r.startswith(_BMS_PREFIX):
yield pycompat.fsencode(r[len(_BMS_PREFIX) :])
def __getitem__(self, k):
return (
self.gitrepo.references[_BMS_PREFIX + pycompat.fsdecode(k)]
.peel()
.id.raw
)
def get(self, k, default=None):
try:
if k in self:
return self[k]
return default
Martin von Zweigbergk
git: don't fail import when pygit2 is not install...
r44968 except gitutil.get_pygit2().InvalidSpecError:
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 return default
@property
def active(self):
h = self.gitrepo.references['HEAD']
if not isinstance(h.target, str) or not h.target.startswith(
_BMS_PREFIX
):
return None
return pycompat.fsencode(h.target[len(_BMS_PREFIX) :])
@active.setter
def active(self, mark):
Josef 'Jeff' Sipek
git: implement basic bookmark activation...
r45114 githead = mark is not None and (_BMS_PREFIX + mark) or None
if githead is not None and githead not in self.gitrepo.references:
raise AssertionError(b'bookmark %s does not exist!' % mark)
self._active = githead
self._aclean = False
def _writeactive(self):
if self._aclean:
return
self.gitrepo.references.create('HEAD', self._active, True)
self._aclean = True
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
def names(self, node):
r = []
for ref in self.gitrepo.listall_references():
if not ref.startswith(_BMS_PREFIX):
continue
if self.gitrepo.references[ref].peel().id.raw != node:
continue
r.append(pycompat.fsencode(ref[len(_BMS_PREFIX) :]))
return r
# Cleanup opportunity: this is *identical* to core's bookmarks store.
def expandname(self, bname):
if bname == b'.':
if self.active:
return self.active
raise error.RepoLookupError(_(b"no active bookmark"))
return bname
def applychanges(self, repo, tr, changes):
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """Apply a list of changes to bookmarks"""
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 # TODO: this should respect transactions, but that's going to
# require enlarging the gitbmstore to know how to do in-memory
# temporary writes and read those back prior to transaction
# finalization.
for name, node in changes:
if node is None:
self.gitrepo.references.delete(
_BMS_PREFIX + pycompat.fsdecode(name)
)
else:
self.gitrepo.references.create(
_BMS_PREFIX + pycompat.fsdecode(name),
gitutil.togitnode(node),
force=True,
)
Josef 'Jeff' Sipek
git: implement a basic checkconflict bookmark store method...
r45113 def checkconflict(self, mark, force=False, target=None):
githead = _BMS_PREFIX + mark
cur = self.gitrepo.references['HEAD']
if githead in self.gitrepo.references and not force:
if target:
if self.gitrepo.references[githead] == target and target == cur:
# re-activating a bookmark
return []
# moving a bookmark - forward?
raise NotImplementedError
raise error.Abort(
_(b"bookmark '%s' already exists (use -f to force)") % mark
)
if len(mark) > 3 and not force:
try:
shadowhash = scmutil.isrevsymbol(self._repo, mark)
except error.LookupError: # ambiguous identifier
shadowhash = False
if shadowhash:
self._repo.ui.warn(
_(
b"bookmark %s matches a changeset hash\n"
b"(did you leave a -r out of an 'hg bookmark' "
b"command?)\n"
)
% mark
)
return []
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
def init(orig, ui, dest=b'.', **opts):
if opts.get('git', False):
windows: use abspath in the git extension...
r48431 path = util.abspath(dest)
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 # TODO: walk up looking for the git repo
_setupdothg(ui, path)
return 0
return orig(ui, dest=dest, **opts)
def reposetup(ui, repo):
Pulkit Goyal
hgit: make sure repository is local before checking for store type...
r45030 if repo.local() and isinstance(repo.store, gitstore):
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 orig = repo.__class__
repo.store._progress_factory = repo.ui.makeprogress
Augie Fackler
git: add debug logging when there's a mismatch in the cached heads list...
r45478 if ui.configbool(b'git', b'log-index-cache-miss'):
repo.store._logfn = repo.ui.warn
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961
class gitlocalrepo(orig):
def _makedirstate(self):
Matt Harbison
git: adapt to some recent dirstate API changes...
r49968 v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
use_dirstate_v2 = v2_req in self.requirements
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 # TODO narrow support here
return dirstate.gitdirstate(
Matt Harbison
git: adapt to some recent dirstate API changes...
r49968 self.ui,
self.vfs,
self.store.git,
use_dirstate_v2,
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 )
def commit(self, *args, **kwargs):
ret = orig.commit(self, *args, **kwargs)
Augie Fackler
git: correctly handle "nothing changed" commits...
r45988 if ret is None:
# there was nothing to commit, so we should skip
# the index fixup logic we'd otherwise do.
return None
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 tid = self.store.git[gitutil.togitnode(ret)].tree.id
# DANGER! This will flush any writes staged to the
# index in Git, but we're sidestepping the index in a
# way that confuses git when we commit. Alas.
self.store.git.index.read_tree(tid)
self.store.git.index.write()
return ret
@property
def _bookmarks(self):
return gitbmstore(self.store.git)
repo.__class__ = gitlocalrepo
return repo
Augie Fackler
git: key off `git` in .hg/requires rather than separate file...
r44977 def _featuresetup(ui, supported):
# don't die on seeing a repo with the git requirement
supported |= {b'git'}
Augie Fackler
git: skeleton of a new extension to _directly_ operate on git repos...
r44961 def extsetup(ui):
extensions.wrapfunction(localrepo, b'makestore', _makestore)
extensions.wrapfunction(localrepo, b'makefilestorage', _makefilestorage)
# Inject --git flag for `hg init`
entry = extensions.wrapcommand(commands.table, b'init', init)
entry[1].extend(
[(b'', b'git', None, b'setup up a git repository instead of hg')]
)
Augie Fackler
git: key off `git` in .hg/requires rather than separate file...
r44977 localrepo.featuresetupfuncs.add(_featuresetup)