Changeset 50302f954e2f…
Parent eb3f0219a412…
by Kevin Gessner <kevin@fogcreek.com>
Changes to 6 files · Browse files at 50302f954e2f Showing diff from parent eb3f0219a412 Diff from another changeset...
@@ -1,4 +1,4 @@ - # Copyright (C) 2009-2011 Fog Creek Software. All rights reserved.
+# Copyright (C) 2009-2013 Fog Creek Software. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
max_push_size = 1000
+
def findoutgoing(repo, other):
try:
# Mercurial 1.6 through 1.8
@@ -34,6 +35,7 @@ # Mercurial 1.5 and lower
return repo.findoutgoing(other, force=False)
+
def prepush(repo, other, force, revs):
try:
from mercurial import discovery
@@ -51,6 +53,7 @@ # Mercurial 1.5 and lower
return repo.prepush(other, False, revs)
+
def remoteui(repo, opts):
if hasattr(cmdutil, 'remoteui'):
# Mercurial 1.5 and lower
@@ -59,6 +62,7 @@ # Mercurial 1.6 and higher
return hg.remoteui(repo, opts)
+
def bigpush(push_fn, ui, repo, dest=None, *files, **opts):
'''Pushes this repository to a target repository.
@@ -99,12 +103,12 @@ ui.debug('pushing: %d\n' % current_push_size)
# force the push, because we checked above that by the time the whole push is done, we'll have merged back to one head
remote_heads = repo.push(other, force=True, revs=outgoing[:current_push_size])
- if remote_heads: # push succeeded
+ if remote_heads: # push succeeded
outgoing = outgoing[current_push_size:]
ui.debug('pushed %d ok\n' % current_push_size)
if push_size < max_push_size:
push_size *= 2
- else: # push failed; try again with a smaller size
+ else: # push failed; try again with a smaller size
push_size /= 2
ui.debug('failed, trying %d\n' % current_push_size)
if push_size == 0:
@@ -113,13 +117,16 @@ ui.status(_('unable to push changeset %s\n') % outgoing[0])
ui.debug('done\n')
+
def parseurl(source):
'''wrap hg.parseurl to work on 1.3 -> 1.5'''
return hg.parseurl(source, None)[:2]
+
def uisetup(ui):
push_cmd = extensions.wrapcommand(commands.table, 'push', bigpush)
push_cmd[1].extend([('', 'chunked', None, 'push large repository in chunks')])
+
class UnpushableChangesetError(Exception):
pass
|
|
|
- # Copyright (C) 2009-2012 Fog Creek Software. All rights reserved.
+# Copyright (C) 2009-2013 Fog Creek Software. All rights reserved.
#
# To enable the "gestalt" extension put these lines in your ~/.hgrc:
# [extensions]
# gestalt = /path/to/gestalt.py
#
# For help on the usage of "hg gestalt" use:
# hg help gestalt
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
'''provides a general overview of your repository state
This extension attempts to help new Mercurial users by providing
several commands to help learn how Mercurial works. The primary
command provided is "hg next", which shows an overview of your
local repository, its relationship and status to its parent,
and what next actions you may wish to consider performing.
'''
import re
import mercurial.__version__
from mercurial import bundlerepo, changegroup, cmdutil, commands, demandimport, hg, node, url, util
from mercurial.i18n import _
from mercurial.error import RepoError
##
# Compatibility shims
# remoteui moved from cmdutil to hg in 1.6.
if hasattr(cmdutil, 'remoteui'):
remoteui = cmdutil.remoteui
else:
remoteui = hg.remoteui
# findoutgoing and findcommonincoming moved from localrepo to
# discovery in 1.6.
demandimport.disable()
try:
from mercurial import localrepo
findcommonincoming = localrepo.localrepository.findcommonincoming
findoutgoing = localrepo.localrepository.findoutgoing
except AttributeError:
from mercurial import discovery
findcommonincoming = discovery.findcommonincoming
try:
# Mercurial <= 1.8
findoutgoing = discovery.findoutgoing
except AttributeError:
# Mercurial >= 1.9
def findoutgoing(repo, remote, force=False):
common, _anyinc, _heads = discovery.findcommonincoming(repo, remote, force=force)
return repo.changelog.findmissing(common)
demandimport.enable()
+
@apply
def _HG_VERSION():
'''return the mercurial version as a tuple rather than a string
Python does the right thing when comparing tuples, so the return
value can be used to compare and detect versions.
'''
version = [0, 0, 0]
parts = [re.match(r'\d+', v).group(0) for v in mercurial.__version__.version.split('.')[:3]]
for i, part in enumerate(map(int, parts)):
version[i] = part
return tuple(version)
+
def parseurl(source):
'''wrap hg.parseurl to work on 1.5 and 1.6
1.5 redefined parseurl()'s return values, and 1.6 split up the
branches parameter into a two-tuple.
'''
uri, branches = hg.parseurl(source, None)[:2]
if _HG_VERSION >= (1, 6, 0):
# branches will be None because we passed None into
# parseuri(), so we can ignore that safely.
hashbranch, branches = branches
else:
# branches will contain one element or fewer because we passed
# None into parseuri().
hashbranch = branches and branches[0] or None
return uri, hashbranch
+
def addbranchrevs(lrepo, repo, hashbranch):
'''wrap hg.addbranchrevs to work on 1.5 and 1.6 and returns the
first value (revs) only
1.5 added the call. 1.6 split up the revs parameter into a
two-tuple.
'''
if _HG_VERSION < (1, 6, 0):
branches = hashbranch and [hashbranch] or []
revs, checkout = hg.addbranchrevs(lrepo, repo, branches, None)
else:
branches = hashbranch, []
revs, checkout = hg.addbranchrevs(lrepo, repo, branches, None)
return revs
+
##
# Private utility functions for determining advice output
def _isrepo(ui, repo, files, opts):
if not repo:
ui.status(_("""You need to create a Mercurial repository.
Run -> hg init
"""))
return True
return False
+
def _ismerging(ui, repo, files, opts):
if repo.dirstate.parents()[1] != node.nullid:
ui.status(_('It appears there is a merge in progress.\n'))
return True
return False
+
def _haschanges(ui, repo, files, opts):
changed = any(repo.status())
if changed:
ui.status(_('''You have changes in your working copy that should be committed
before updating your local or remote repositories:
Run -> hg commit
'''))
return True
return False
+
def _shouldmerge(ui, repo, files, opts):
heads = repo.branchheads(closed=False)
if len(heads) > 1:
ui.status(_('''You have two heads in your local repository. To resolve this,
you should merge:
Run -> hg merge
'''))
return True
return False
+
def _shouldsync(ui, repo, files, opts):
source, hashbranch = parseurl(ui.expandpath('default'))
# grab incoming and outgoing changesets
try:
other = hg.repository(remoteui(repo, opts), source)
except RepoError:
ui.status(_('''You have not set a default repository in your configuration file.
Edit your configuration file, .hg/hgrc, and add a default entry in
the [paths] section.
'''))
return True
revs = addbranchrevs(repo, other, hashbranch)
ui.pushbuffer()
common, incoming, rheads = findcommonincoming(repo, other, heads=revs, force=False)
ui.popbuffer()
if incoming:
ui.status(_('''There are changes in your remote repository that haven't been
included in your local repository. To get your copy up-to-date you should:
Run -> hg pull
'''))
return True
source, hashbranch = parseurl(source)
other = hg.repository(remoteui(repo, opts), source)
revs = addbranchrevs(repo, other, hashbranch)
ui.pushbuffer()
outgoing = findoutgoing(repo, other, force=False)
if outgoing:
outgoing = repo.changelog.nodesbetween(outgoing, revs)[0]
ui.popbuffer()
if outgoing:
ui.status(_('''You have changes in your local repository that aren't in your
remote repository. If you want to share your changes, you should:
Run -> hg push
'''))
return True
return False
+
def _istip(ui, repo, files, opts):
tip = repo['tip']
cwd = repo['.']
if tip != cwd:
ui.status(_('''You are not at a head. You probably want to update to tip
before making any changes:
Run -> hg up
'''))
return True
return False
+
def _shouldwritemorecode(ui, *ignored):
ui.status(_('Everything is up-to-date. Write more code!\n'))
return True
+
##
# General utility methods
def outgoing(repo, origin):
'''return a list of outgoing changesets'''
out = findoutgoing(repo, origin)
if out:
out = repo.changelog.nodesbetween(out, None)[0]
return out
+
def incoming(repo, origin, revs):
'''return a list of incoming changesets'''
if revs:
revs = [origin.lookup(rev) for rev in revs]
common, incoming, rheads = findcommonincoming(repo, origin, heads=revs, force=False)
if not incoming:
return []
if not origin.local():
# create a bundle (uncompressed if other repo is not local)
if not revs and origin.capable('changegroupsubset'):
revs = rheads
if not revs:
cg = origin.changegroup(incoming, 'incoming')
else:
cg = origin.changegroupsubset(incoming, revs, 'incoming')
- fname = changegroup.writebundle(cg, None, "HG10UN")
+ fname = changegroup.writebundle(cg, None, "HG10UN")
origin = bundlerepo.bundlerepository(repo.ui, repo.root, fname)
incoming = origin.changelog.findmissing(common, revs)
if hasattr(origin, 'close'):
origin.close()
return incoming
+
##
# commands
def overview(ui, repo, source=None, **opts):
'''provides a general overview of your repository state
This command combines the output of the hg incomng, hg outgoing,
hg status, and hg id commands into an easily human-readable explanation
of the entire state of your current working repository.
'''
if not repo:
return
originurl = ui.expandpath(source or 'default')
targeturl = ui.expandpath(source or 'default-push', source or 'default')
origin, hashbranch = parseurl(originurl)
try:
origin = hg.repository(remoteui(repo, opts), origin)
except RepoError:
return
target, hashbranch = parseurl(targeturl)
target = hg.repository(remoteui(repo, opts), target)
try:
# Mercurial >= 1.9
hidepassword = util.hidepassword
except AttributeError:
# Mercurial <= 1.8
hidepassword = url.hidepassword
if originurl == targeturl:
ui.status(_('parent repository: %s\n') % hidepassword(originurl))
else:
ui.status(_('source repository: %s\n') % hidepassword(getattr(origin, 'root', origin.url())))
ui.status(_('destination repository: %s\n') % hidepassword(getattr(target, 'root', target.url())))
ui.pushbuffer()
out = outgoing(repo, target)
inc = incoming(repo, origin, filter(bool, [hashbranch]))
ui.popbuffer()
changed = any(repo.status())
if changed:
status = _('uncommitted changes')
else:
status = _('working copy up-to-date')
# grab heads
heads = repo.branchheads(None, closed=False)
if len(heads) > 1:
merge = 'merge required'
else:
merge = ''
ui.status(_('| Remote | << %s | Local | %s\n') % (str(len(out)).center(5), merge))
ui.status(_('| Repository | %s >> | Repository | %s\n') % (str(len(inc)).center(5), status))
if opts['detail']:
if len(out) > 0:
ui.status(_('\noutgoing changes:\n'))
for rev in out:
ui.status('%s %s\n' % (repo[rev],
repo[rev].description().strip().split('\n')[0]))
if len(inc) > 0:
ui.status(_('\nincoming changes:\n'))
for rev in inc:
ui.status('%s %s\n' % (repo[rev],
repo[rev].description().strip().split('\n')[0]))
if changed:
ui.status(_('\nlocal files:\n'))
ui.pushbuffer()
commands.status(ui, repo, '', **opts)
status = ui.popbuffer()
for l in status.splitlines():
print ' %s' % l
+
def advice(ui, repo, *files, **opts):
'''provides a suggestion of your next step
This command attempts to help new Mercurial users by suggesting
what your next step should be. These steps are suggestions only,
and do not provide an exhaustive list of all possible actions that
may be appropriate, but should nevertheless help you if you are
unsure how to proceed.
'''
checks = [_isrepo,
_ismerging,
_haschanges,
_shouldmerge,
_shouldsync,
_istip,
_shouldwritemorecode]
for fun in checks:
if fun(ui, repo, files, opts):
return
+
def next(ui, repo, *files, **opts):
'''provides an overview and explanation of what to do next
This command shows you a graphical representation of the
current state of your repository and its parent, and suggests
what your next step should be based on the picture.'''
overview(ui, repo, *files, **opts)
advice(ui, repo, *files, **opts)
cmdtable = {
'overview':
(overview,
[('d', 'detail', None, _('provide verbose output'))],
_('hg gestalt [OPTION] [REMOTE REPOSITORY]')),
'advice':
(advice, [], _('hg next')),
'next|wtf':
(next,
[('d', 'detail', None, _('provide verbose output'))],
_('hg next [OPTION] [REMOTE REPOSITORY]')),
- }
+}
commands.optionalrepo += 'wtf advice overview next'
|
|
|
@@ -1,4 +1,4 @@ - # Copyright (C) 2011, 2012 Fog Creek Software. All rights reserved.
+# Copyright (C) 2011-2013 Fog Creek Software. All rights reserved.
#
# To enable the "kiln" extension put these lines in your ~/.hgrc:
# [extensions]
@@ -40,6 +40,7 @@pushing to Kiln. See :hg:`help push` and
http://kiln.stackexchange.com/questions/4679/ for more information.
'''
+
import itertools
import os
import re
@@ -52,7 +53,7 @@from cookielib import MozillaCookieJar
from hashlib import md5
from mercurial import (commands, cmdutil, demandimport, extensions, hg,
- localrepo, match, util)
+ localrepo, match, util)
from mercurial import ui as hgui
from mercurial import url as hgurl
from mercurial.error import RepoError
@@ -73,17 +74,20 @@
try:
import webbrowser
+
def browse(url):
webbrowser.open(escape_reserved(url))
except ImportError:
if os.name == 'nt':
import win32api
+
def browse(url):
win32api.ShellExecute(0, 'open', escape_reserved(url), None, None, 0)
demandimport.enable()
_did_version_check = False
+
class APIError(Exception):
def __init__(self, obj):
'''takes a json object for debugging
@@ -95,6 +99,7 @@ def __str__(self):
return '\n'.join('%s: %s' % (k, v) for k, v in self.errors.items())
+
class Review(dict):
def __init__(self, json, ui=None, token=None, baseurl=None):
self.ui = ui
@@ -119,29 +124,32 @@ 'token': self.token,
'ixBug': self.key,
'revs': revs,
- }
+ }
call_api(self.ui, self.baseurl, 'Api/1.0/Repo/%d/CaseAssociation/Create' % ixRepo, params, post=True)
else:
params = {
'token': self.token,
'revs': revs,
'ixRepo': ixRepo,
- }
+ }
call_api(self.ui, self.baseurl, 'Api/2.0/Review/%s/Association/Create' % self.key, params, post=True)
return urljoin(self.baseurl, 'Review', self.key)
@classmethod
def get_reviews(klass, ui, token, baseurl, ixRepo):
- review_lists = call_api(ui, baseurl, 'Api/2.0/Reviews', dict(token=token))
+ review_lists = call_api(ui, baseurl, 'Api/2.0/Reviews', dict(token=token, fReviewed="false", fAwaitingReview="false", nDaysActive=14))
reviews = {}
for key, review_list in review_lists.iteritems():
- if not key.startswith('reviews'): continue
+ if not key.startswith('reviews'):
+ continue
for review in review_list:
review = Review(review, ui, token, baseurl)
- if not review.belongs_to(ixRepo): continue
+ if not review.belongs_to(ixRepo):
+ continue
reviews[review.key.lower()] = review
return reviews
+
def urljoin(*components):
url = components[0]
for next in components[1:]:
@@ -152,6 +160,7 @@ url += next
return url
+
def _baseurl(ui, path):
try:
url = str(util.url(util.removeauth(path)))
@@ -168,12 +177,13 @@ else:
return None
+
def escape_reserved(path):
reserved = re.compile(
- r'^(((com[1-9]|lpt[1-9]|con|prn|aux)(\..*)?)|web\.config' +
- r'|clock\$|app_data|app_code|app_browsers' +
- r'|app_globalresources|app_localresources|app_themes' +
- r'|app_webreferences|bin|.*\.(cs|vb)html?)$', re.IGNORECASE)
+ r'^(((com[1-9]|lpt[1-9]|con|prn|aux)(\..*)?)|web\.config' +
+ r'|clock\$|app_data|app_code|app_browsers' +
+ r'|app_globalresources|app_localresources|app_themes' +
+ r'|app_webreferences|bin|.*\.(cs|vb)html?|.*\.(svc|xamlx|xoml|rules))$', re.IGNORECASE)
p = path.split('?')
path = p[0]
query = '?' + p[1] if len(p) > 1 else ''
@@ -182,9 +192,11 @@ else part
for part in path.split('/')) + query
+
def normalize_name(s):
return s.lower().replace(' ', '-')
+
def call_api(ui, baseurl, urlsuffix, params, post=False):
'''returns the json object for the url and the data dictionary
@@ -206,14 +218,18 @@ raise util.Abort(_('kiln: an error occurred while trying to reach %s') % url)
if isinstance(obj, dict) and 'errors' in obj:
- if 'token' in params and obj['errors'][0]['codeError'] == 'InvalidToken':
+ error_code = obj['errors'][0]['codeError']
+ if 'token' in params and error_code == 'InvalidToken':
token = login(ui, baseurl)
add_kilnapi_token(ui, baseurl, token)
params['token'] = token
return call_api(ui, baseurl, urlsuffix, params, post)
+ elif error_code == 'BadAuthentication':
+ raise util.Abort(_('authorization failed'))
raise APIError(obj)
return obj
+
def login(ui, url):
ui.write(_('realm: %s\n') % url)
user = ui.prompt('username:')
@@ -225,6 +241,7 @@ return token
raise util.Abort(_('authorization failed'))
+
def get_domain(url):
temp = url[url.find('://') + len('://'):]
domain = temp[:temp.find('/')]
@@ -236,6 +253,7 @@
return domain
+
def _get_path(path):
if os.name == 'nt':
ret = os.path.expanduser('~\\_' + path)
@@ -246,6 +264,7 @@ ret = re.sub(r'([A-Za-z]):', r'\1:\\', ret)
return ret
+
def _upgradecheck(ui, repo):
global _did_version_check
if _did_version_check or not ui.configbool('kiln', 'autoupdate', True):
@@ -253,6 +272,7 @@ _did_version_check = True
_upgrade(ui, repo)
+
def _upgrade(ui, repo):
ext_dir = os.path.dirname(os.path.abspath(__file__))
ui.debug(_('kiln: checking for extensions upgrade for %s\n') % ext_dir)
@@ -282,6 +302,7 @@ ui.debug(_('kiln: error updating extensions: %s\n') % e)
ui.debug(_('kiln: traceback: %s\n') % traceback.format_exc())
+
def is_dest_a_path(ui, dest):
paths = ui.configitems('paths')
for pathname, path in paths:
@@ -289,6 +310,7 @@ return True
return False
+
def is_dest_a_scheme(ui, dest):
destscheme = dest[:dest.find('://')]
if destscheme:
@@ -297,12 +319,14 @@ return True
return False
+
def create_match_list(matchlist):
ret = ''
for m in matchlist:
ret += ' ' + m + '\n'
return ret
+
def get_username(url):
url = re.sub(r'https?://', '', url)
url = re.sub(r'/.*', '', url)
@@ -316,6 +340,7 @@ # Didn't find anything...
return ''
+
def get_dest(ui):
from mercurial.dispatch import _parse
try:
@@ -332,6 +357,7 @@ dest = 'default'
return ui.expandpath(dest)
+
def check_kilnapi_token(ui, url):
tokenpath = _get_path('hgkiln')
@@ -355,6 +381,7 @@ fp.close()
return ret
+
def add_kilnapi_token(ui, url, fbToken):
if not fbToken:
return
@@ -369,12 +396,14 @@ fp.write(domain + ' ' + userhash + ' ' + fbToken + '\n')
fp.close()
+
def delete_kilnapi_tokens():
# deletes the hgkiln file
tokenpath = _get_path('hgkiln')
if os.path.exists(tokenpath) and not os.path.isdir(tokenpath):
os.remove(tokenpath)
+
def check_kilnauth_token(ui, url):
cookiepath = _get_path('hgcookies')
if (not os.path.exists(cookiepath)) or (not os.path.isdir(cookiepath)):
@@ -396,14 +425,17 @@ if cookie.name == 'fbToken':
return cookie.value
+
def remember_path(ui, repo, path, value):
'''appends the path to the working copy's hgrc and backs up the original'''
paths = dict(ui.configitems('paths'))
# This should never happen.
- if path in paths: return
+ if path in paths:
+ return
# ConfigParser only cares about these three characters.
- if re.search(r'[:=\s]', path): return
+ if re.search(r'[:=\s]', path):
+ return
try:
audit_path = scmutil.pathauditor(repo.root)
@@ -428,6 +460,7 @@ except IOError:
return
+
def unremember_path(ui, repo):
'''restores the working copy's hgrc'''
@@ -446,6 +479,7 @@ else:
os.remove(hgrc)
+
def guess_kilnpath(orig, ui, repo, dest=None, **opts):
if not dest:
return orig(ui, repo, **opts)
@@ -465,24 +499,24 @@
if (ndest.count('/') == 0 and
(ntarget[0] == ndest or
- ntarget[1] == ndest or
- ntarget[2] == ndest or
- ndest in aliases)):
+ ntarget[1] == ndest or
+ ntarget[2] == ndest or
+ ndest in aliases)):
matches.append(url)
elif (ndest.count('/') == 1 and
- '/'.join(ntarget[0:2]) == ndest or
- '/'.join(ntarget[1:3]) == ndest):
+ '/'.join(ntarget[0:2]) == ndest or
+ '/'.join(ntarget[1:3]) == ndest):
matches.append(url)
elif (ndest.count('/') == 2 and
- '/'.join(ntarget[0:3]) == ndest):
+ '/'.join(ntarget[0:3]) == ndest):
matches.append(url)
if (ntarget[0].startswith(ndest) or
- ntarget[1].startswith(ndest) or
- ntarget[2].startswith(ndest) or
- '/'.join(ntarget[0:2]).startswith(ndest) or
- '/'.join(ntarget[1:3]).startswith(ndest) or
- '/'.join(ntarget[0:3]).startswith(ndest)):
+ ntarget[1].startswith(ndest) or
+ ntarget[2].startswith(ndest) or
+ '/'.join(ntarget[0:2]).startswith(ndest) or
+ '/'.join(ntarget[1:3]).startswith(ndest) or
+ '/'.join(ntarget[0:3]).startswith(ndest)):
prefixmatches.append(url)
if len(matches) == 0:
@@ -503,6 +537,7 @@ finally:
unremember_path(ui, repo)
+
def get_tails(repo):
tails = []
for rev in xrange(repo['tip'].rev() + 1):
@@ -513,6 +548,7 @@ raise util.Abort(_('Path guessing is only enabled for non-empty repositories.'))
return tails
+
def get_targets(repo):
def get_kiln_repo_url_prefix(default_prefix):
'''Checks repo paths and returns server url for ssh:. For http(s) falls back to default_prefix.'''
@@ -536,9 +572,10 @@ related_repo['sProjectSlug'],
related_repo['sGroupSlug'],
related_repo['sSlug'],
- related_repo.get('rgAliases', [])] for related_repo in related_repos])
+ [a['sSlug'] for a in related_repo.get('rgAliases', [])]] for related_repo in related_repos])
return targets
+
def display_targets(repo):
targets = get_targets(repo)
repo.ui.write(_('The following Kiln targets are available for this repository:\n\n'))
@@ -549,6 +586,7 @@ alias_text = ''
repo.ui.write(' %s/%s/%s/%s%s\n' % (target[0], target[1], target[2], target[3], alias_text))
+
def get_token(ui, url):
'''Checks for an existing API token. If none, returns a new valid token.'''
token = check_kilnapi_token(ui, url)
@@ -560,6 +598,7 @@ add_kilnapi_token(ui, url, token)
return token
+
def get_api_url(url):
'''Given a URL, returns the URL of the Kiln installation.'''
if '/kiln/' in url.lower():
@@ -570,6 +609,7 @@ baseurl = url
return baseurl
+
class HTTPNoRedirectHandler(urllib2.HTTPRedirectHandler):
def http_error_302(self, req, fp, code, msg, headers):
# Doesn't allow multiple redirects so repo alias URLs will not
@@ -578,6 +618,7 @@
http_error_301 = http_error_303 = http_error_307 = http_error_302
+
def get_repo_record(repo, url, token=None):
'''Returns a Kiln repository record that corresponds to the given repo.'''
baseurl = get_api_url(url)
@@ -585,7 +626,7 @@ token = get_token(repo.ui, baseurl)
try:
- data = urllib.urlencode({ 'token': token }, doseq=True)
+ data = urllib.urlencode({'token': token}, doseq=True)
opener = urllib2.build_opener(HTTPNoRedirectHandler)
urllib2.install_opener(opener)
fd = urllib2.urlopen(url + '?' + data)
@@ -614,6 +655,7 @@ repo = find_slug(repo, group, 'repos')
return repo
+
def new_branch(repo, url, name):
'''Creates a new, decentralized branch off of the specified repo.'''
baseurl = get_api_url(url)
@@ -634,33 +676,40 @@ return
raise
+
def normalize_user(s):
'''Takes a Unicode string and returns an ASCII string.'''
return unicodedata.normalize('NFKD', s).encode('ASCII', 'ignore')
+
def encode_out(s):
'''Takes a Unicode string and returns a string encoded for output.'''
return s.encode(sys.stdout.encoding, 'ignore')
+
def record_base(ui, repo, node, **kwargs):
'''Stores the first changeset committed in the repo UI so we do not need to expensively recalculate.'''
repo.ui.setconfig('kiln', 'node', node)
+
def walk(repo, revs):
'''Returns revisions in repo specified by the string revs'''
return cmdutil.walkchangerevs(repo, match.always(repo.root, None), {'rev': [revs.encode('ascii', 'ignore')]}, lambda *args: None)
+
def print_list(ui, l, header):
'''Prints a list l to ui using list notation, with header being the first line'''
ui.write(_('%s\n' % header))
for item in l:
ui.write(_('- %s\n') % item)
+
def wrap_push(orig, ui, repo, dest=None, **opts):
'''Wraps `hg push' so a review will be created after path guessing and a successful push.'''
guess_kilnpath(orig, ui, repo, dest, **opts)
review(ui, repo, dest, opts)
+
def add_unique_reviewer(ui, reviewer, reviewers, name_to_ix, ix_to_name):
'''Adds a reviewer to reviewers if it is not already added. Otherwise, print an error.'''
if name_to_ix[reviewer] in reviewers:
@@ -669,6 +718,7 @@ reviewers.append(name_to_ix[reviewer])
print_list(ui, [ix_to_name[r] for r in reviewers], 'reviewers:')
+
def review(ui, repo, dest, opts):
'''Associates the pushed changesets with a new or existing Kiln review.'''
if not opts['review'] or not repo.ui.config('kiln', 'node'):
@@ -770,7 +820,7 @@ 'ixReviewers': reviewers,
'sTitle': '(Multiple changesets)' if len(revs) > 1 else repo[revs[0]].description(),
'sDescription': 'Review created from push.'
- }
+ }
r = Review(call_api(repo.ui, baseurl, 'Api/2.0/Review/Create', params, post=True))
ui.write(_('new review created: %s\n' % urljoin(baseurl, 'Review', r.key)))
else:
@@ -778,6 +828,7 @@ url = reviews[choice].associate(kiln_repo['ixRepo'], revs)
ui.write(_('updated review: %s\n' % url))
+
def dummy_command(ui, repo, dest=None, **opts):
'''dummy command to pass to guess_path() for hg kiln
@@ -786,6 +837,7 @@ '''
return opts['path'] != dest and dest or None
+
def _standin_expand(paths):
'''given a sequence of filenames, returns a set of filenames --
relative to the current working directory! -- prefixed with all
@@ -795,6 +847,7 @@ choices = [[p, os.path.join('.kbf', p), os.path.join('.hglf', p)] for p in paths]
return set(itertools.chain(*choices))
+
def _filename_match(repo, ctx, paths):
'''returns a set of filenames contained in both paths and the
ctx's manifest, accounting for standins'''
@@ -812,6 +865,7 @@ haystacks = set(map(os.path.normpath, haystacks))
return needles.intersection(haystacks)
+
def kiln(ui, repo, **opts):
'''show the relevant page of the repository in Kiln
@@ -894,6 +948,7 @@ if default or opts['changes']:
browse(url)
+
def uisetup(ui):
extensions.wrapcommand(commands.table, 'outgoing', guess_kilnpath)
extensions.wrapcommand(commands.table, 'pull', guess_kilnpath)
@@ -902,6 +957,7 @@ # Add --review as a valid flag to push's command table
push_cmd[1].extend([('', 'review', None, 'associate changesets with Kiln review')])
+
def reposetup(ui, repo):
try:
from mercurial.httprepo import httprepository
@@ -913,6 +969,7 @@ _upgradecheck(ui, repo)
repo.ui.setconfig('hooks', 'outgoing.kilnreview', 'python:kiln.record_base')
+
def extsetup(ui):
try:
g = extensions.find('gestalt')
@@ -943,4 +1000,4 @@ ('n', 'new-branch', '', _('asynchronously create a new branch from the current repository')),
('', 'logout', None, _('log out of Kiln sessions'))],
_('hg kiln [-p url] [-r rev|-a file|-f file|-c|-o|-s|-t|-n branchName|--logout]'))
- }
+}
|
|
@@ -0,0 +1,59 @@ + # Copyright (C) 2011, 2012 Fog Creek Software. All rights reserved.
+#
+# This extension is used internally by Kiln Importer.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
+
+'''fix a _filecache invalidation bug so hg convert works
+
+See
+
+ * http://our.fogbugz.com/f/cases/2338914/importer-does-not-work-with-hg-2-3-1 and
+ * http://www.selenic.com/pipermail/mercurial-devel/2012-November/046159.html
+
+ for more details. Until Mercurial fixes this bug or accepts my patch,
+this bug affects any tags in this revset:
+
+ * hg log -r 'descendants(9f94358) and tag()' --template '{tags}\n'
+
+As of writing, that's every 2.3 release and 2.4-rc and 2.4.
+'''
+
+from mercurial import util
+
+def reposetup(ui, repo):
+ if util.version()[:3] < '2.3':
+ # This bug was first introduced in 9f94358f9f93, which
+ # occurred between 2.3-rc and 2.3.
+ return
+
+ from mercurial import localrepo
+ if not issubclass(repo.__class__, localrepo.localrepository):
+ return
+
+ class kilnfilecacherepo(repo.__class__):
+ def destroyed(self, *args, **kwargs):
+ # By saving _filecache before it's cleared, we can
+ # restore it and then call invalidate() afterward so
+ # localrepo can delattr() the relevant attributes off
+ # the repo object. See my mercurial-devel patch for
+ # more details.
+ oldfilecache = dict(self._filecache)
+ super(kilnfilecacherepo, self).destroyed(*args, **kwargs)
+ self._filecache = oldfilecache
+ self.invalidate()
+
+ repo.__class__ = kilnfilecacherepo
|
@@ -1,4 +1,4 @@ - # Copyright (C) 2009-2011 Fog Creek Software. All rights reserved.
+# Copyright (C) 2009-2013 Fog Creek Software. All rights reserved.
#
# To enable the "kilnpath" extension put these lines in your ~/.hgrc:
# [extensions]
@@ -18,7 +18,7 @@# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-'''Lets users identify repository paths by custom schemes like kiln://Project/Group/Repository.
+'''allow users identify repository paths by custom schemes like kiln://Project/Group/Repository (DEPRECATED)
This extensions knows how to expand Kiln repository paths from
kiln://Project/Group/Repository to
@@ -31,10 +31,36 @@To specify the Kiln path prefix, add a [kiln_scheme] section to your hg config like so:
[kiln_scheme]
kiln = https://your.fogbugz.com/kiln/Repo
+
+This extensions is deprecated, and does not work in Mercurial 2.4 and later.
+You should use the built-in `schemes` extension instead instead. Type `hg help schemes`
+for more information.
'''
-import os
-from mercurial import extensions, commands, hg, ui
+import re
+
+from mercurial import hg, ui, util, __version__
+
+
+@apply
+def _HG_VERSION():
+ '''return the mercurial version as a tuple rather than a string
+
+ Python does the right thing when comparing tuples, so the return
+ value can be used to compare and detect versions.
+ '''
+ version = [0, 0, 0]
+ parts = [re.match(r'\d+', v).group(0) for v in __version__.version.split('.')[:3]]
+ for i, part in enumerate(map(int, parts)):
+ version[i] = part
+ return tuple(version)
+
+
+if _HG_VERSION >= (2, 3, 0):
+ raise util.Abort(
+ 'kilnpath is deprecated, and does not work in Mercurial 2.3 or higher. '
+ 'Use the official schemes extension instead')
+
def urlcombine(pre, suff):
if pre.endswith("/"):
@@ -43,11 +69,13 @@ suff = "/" + suff
return pre + suff
+
def add_schemes(ui):
schemes = ui.configitems('kiln_scheme')
for scheme, prefix in schemes:
hg.schemes[scheme] = KilnRepo(scheme, prefix)
+
class KilnRepo(object):
def __init__(self, scheme, prefix):
self.scheme = scheme
@@ -70,9 +98,9 @@ # Mercurial <= 1.8
return hg._lookup(path_full).instance(ui, path_full, create)
+
def extsetup(*args):
if len(args) > 0:
add_schemes(args[0])
else:
add_schemes(ui.ui())
-
|
@@ -3,7 +3,8 @@ import zipfile
def compile_extensions():
- compileall.compile_dir(os.path.dirname(__file__), force=1)
+ curdir = os.path.dirname(os.path.abspath(__file__))
+ compileall.compile_dir(curdir, force=True)
def walk(dir_root):
for dirpath, dirnames, filenames in os.walk(dir_root):
|
Loading...