infinitepush: bookmarks remote-list

Summary:
Add an option to list remote and scratch bookmarks by pattern.
Only very simple pattern is allowed: either the literal string
or a prefix (like 'scratch/stash/*'). It was made intentionally
to make sure that pattern requests are fast in mysql.

Mysql tests will be added to the integration tests

Test Plan: Run `test-infinitepush-*`

Reviewers: mitrandir, andrasbelo, durham

Reviewed By: durham

Subscribers: mjpieters, #sourcecontrol

Differential Revision: https://phabricator.intern.facebook.com/D4074409

Tasks: 12479677

Signature: t1:4074409:1477500968:e91cd2505d61a2f1db30c7f00cdcfc949e433507
This commit is contained in:
Stanislau Hlebik 2016-10-26 10:03:35 -07:00
parent 6086c2f0cb
commit 005a9ace91
4 changed files with 117 additions and 16 deletions

View File

@ -167,6 +167,16 @@ def clientextsetup(ui):
('', 'bundle-store', None,
_('force push to go to bundle store (EXPERIMENTAL)')))
bookcmd = extensions.wrapcommand(commands.table, 'bookmarks', exbookmarks)
bookcmd[1].append(
('', 'list-remote', '',
'list remote bookmarks. '
'Use \'*\' to find all bookmarks with the same prefix',
'PATTERN'))
bookcmd[1].append(
('', 'remote-path', '',
'name of the remote path to list the bookmarks'))
wrapcommand(commands.table, 'pull', _pull)
wrapfunction(discovery, 'checkheads', _checkheads)
@ -182,6 +192,32 @@ def clientextsetup(ui):
partorder.insert(
index, partorder.pop(partorder.index(scratchbranchparttype)))
def _showbookmarks(ui, bookmarks, **opts):
# Copy-paste from commands.py
fm = ui.formatter('bookmarks', opts)
for bmark, n in sorted(bookmarks.iteritems()):
fm.startitem()
if not ui.quiet:
fm.plain(' ')
fm.write('bookmark', '%s', bmark)
pad = ' ' * (25 - encoding.colwidth(bmark))
fm.condwrite(not ui.quiet, 'node', pad + ' %s', n)
fm.plain('\n')
fm.end()
def exbookmarks(orig, ui, repo, *names, **opts):
pattern = opts.get('list_remote')
if pattern:
remotepath = opts.get('remote_path')
path = ui.paths.getpath(remotepath or None, default=('default'))
destpath = path.pushloc or path.loc
other = hg.peer(repo, opts, destpath)
fetchedbookmarks = other.listkeyspatterns('bookmarks',
patterns=[pattern])
_showbookmarks(ui, fetchedbookmarks, **opts)
return
return orig(ui, repo, *names, **opts)
def _checkheads(orig, pushop):
if pushop.ui.configbool(experimental, configscratchpush, False):
return
@ -196,20 +232,15 @@ def localrepolistkeys(orig, self, namespace, patterns=None):
if namespace == 'bookmarks' and patterns:
index = self.bundlestore.index
results = {}
patterns = set(patterns)
# TODO(stash): this function has a limitation:
# patterns are not actually patterns, just simple string comparison
for bookmark in patterns:
if _scratchbranchmatcher(bookmark):
# TODO(stash): use `getbookmarks()` method
node = index.getnode(bookmark)
if node:
results[bookmark] = node
bookmarks = orig(self, namespace)
for bookmark, node in bookmarks.items():
if bookmark in patterns:
results[bookmark] = node
for pattern in patterns:
results.update(index.getbookmarks(pattern))
if pattern.endswith('*'):
pattern = 're:^' + pattern[:-1] + '.*'
kind, pat, matcher = util.stringmatcher(pattern)
for bookmark, node in bookmarks.items():
if matcher(bookmark):
results[bookmark] = node
return results
else:
return orig(self, namespace)

View File

@ -4,6 +4,8 @@ from indexapi import (
indexapi,
)
from mercurial import util
class fileindexapi(indexapi):
def __init__(self, repo):
super(fileindexapi, self).__init__()
@ -36,6 +38,20 @@ class fileindexapi(indexapi):
bookmarkpath = os.path.join(self._bookmarkmap, bookmark)
return self._read(bookmarkpath)
def getbookmarks(self, query):
result = {}
if query.endswith('*'):
query = 're:^' + query[:-1] + '.*'
kind, pat, matcher = util.stringmatcher(query)
prefixlen = len(self._bookmarkmap) + 1
for dirpath, _, books in self._repo.vfs.walk(self._bookmarkmap):
for book in books:
bookmark = os.path.join(dirpath, book)[prefixlen:]
if not matcher(bookmark):
continue
result[bookmark] = self._read(os.path.join(dirpath, book))
return result
def _write(self, path, value):
vfs = self._repo.vfs
dirname = vfs.dirname(path)

View File

@ -176,9 +176,31 @@ class sqlindexapi(indexapi):
if len(result) != 1 or len(result[0]) != 1:
self.log.info("No matching bookmark")
return None
bookmark = result[0][0]
self.log.info("Found node %r" % bookmark)
return bookmark
node = result[0][0]
self.log.info("Found node %r" % node)
return node
def getbookmarks(self, query):
if not self._connected:
self.sqlconnect()
self.log.info(
"QUERY BOOKMARKS reponame: %r query: %r" % (self.reponame, query))
query = query.replace('_', '\\_')
query = query.replace('%', '\\%')
if query.endswith('*'):
query = query[:-1] + '%'
self.sqlcursor.execute(
"SELECT bookmark, node from bookmarkstonode WHERE "
"reponame = %s AND bookmark LIKE %s",
params=(self.reponame, query))
result = self.sqlcursor.fetchall()
bookmarks = {}
for row in result:
if len(row) != 2:
self.log.info("Bad row returned: %s" % row)
continue
bookmarks[row[0]] = row[1]
return bookmarks
class CustomConverter(mysql.connector.conversion.MySQLConverter):
"""Ensure that all values being returned are returned as python string

View File

@ -398,3 +398,35 @@ Use --force because this push creates new head
|/
o initialcommit public
$ hg book --list-remote scratch/*
scratch/anotherbranch 1de1d7d92f8965260391d0513fe8a8d5973d3042
scratch/mybranch 8872775dd97a750e1533dc1fbbca665644b32547
$ hg book --list-remote scratch/my
$ hg book --list-remote scratch/my*
scratch/mybranch 8872775dd97a750e1533dc1fbbca665644b32547
$ hg book --list-remote scratch/my* -T json
[
{
"bookmark": "scratch/mybranch",
"node": "8872775dd97a750e1533dc1fbbca665644b32547"
}
]
$ cd ../repo
$ hg book scratch/serversidebook
$ hg book serversidebook
$ cd ../client
$ hg book --list-remote scratch/* -T json
[
{
"bookmark": "scratch/anotherbranch",
"node": "1de1d7d92f8965260391d0513fe8a8d5973d3042"
},
{
"bookmark": "scratch/mybranch",
"node": "8872775dd97a750e1533dc1fbbca665644b32547"
},
{
"bookmark": "scratch/serversidebook",
"node": "0000000000000000000000000000000000000000"
}
]