##// END OF EJS Templates
locking: take the `wlock` for the full `hg addremove` duration...
locking: take the `wlock` for the full `hg addremove` duration Otherwise, there is a race condition window between the time we resolve the file to addremove with the matcher and the time we lock the repo and modify the dirstate. For example, the working copy might have been updated away, or purged, and the matched files would no longer be correct.

File last commit:

r50279:3b8fce9a stable
r50902:2aacd560 default
Show More
__init__.py
352 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):
av6
git: make sure to fsdecode bookmark names everywhere (issue6723)
r50279 githead = None
if mark is not None:
githead = _BMS_PREFIX + pycompat.fsdecode(mark)
Josef 'Jeff' Sipek
git: implement basic bookmark activation...
r45114 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):
av6
git: make sure to fsdecode bookmark names everywhere (issue6723)
r50279 githead = _BMS_PREFIX + pycompat.fsdecode(mark)
Josef 'Jeff' Sipek
git: implement a basic checkconflict bookmark store method...
r45113 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)