tlib/codereview: update from Go - plan9port - [fork] Plan 9 from user space | |
git clone git://src.adamsgaard.dk/plan9port | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 63550fce0e1984590710f332abb46a4c73491ebd | |
parent 7276d83b825163fc76c905c21ba3eab078e53dc8 | |
Author: Russ Cox <[email protected]> | |
Date: Sat, 14 Jul 2012 08:01:47 -0400 | |
lib/codereview: update from Go | |
R=rsc | |
http://codereview.appspot.com/6396045 | |
Diffstat: | |
M lib/codereview/codereview.py | 156 ++++++++++++++++++++++++-----… | |
1 file changed, 121 insertions(+), 35 deletions(-) | |
--- | |
diff --git a/lib/codereview/codereview.py b/lib/codereview/codereview.py | |
t@@ -895,7 +895,7 @@ def CheckFormat(ui, repo, files, just_warn=False): | |
# Check that gofmt run on the list of files does not change them | |
def CheckGofmt(ui, repo, files, just_warn): | |
- files = [f for f in files if (not f.startswith('test/') or f.startswit… | |
+ files = gofmt_required(files) | |
if not files: | |
return | |
cwd = os.getcwd() | |
t@@ -925,7 +925,7 @@ def CheckGofmt(ui, repo, files, just_warn): | |
# Check that *.[chys] files indent using tabs. | |
def CheckTabfmt(ui, repo, files, just_warn): | |
- files = [f for f in files if f.startswith('src/') and re.search(r"\.[c… | |
+ files = [f for f in files if f.startswith('src/') and re.search(r"\.[c… | |
if not files: | |
return | |
cwd = os.getcwd() | |
t@@ -955,16 +955,26 @@ def CheckTabfmt(ui, repo, files, just_warn): | |
####################################################################### | |
# CONTRIBUTORS file parsing | |
-contributors = {} | |
+contributorsCache = None | |
+contributorsURL = None | |
def ReadContributors(ui, repo): | |
- global contributors | |
+ global contributorsCache | |
+ if contributorsCache is not None: | |
+ return contributorsCache | |
+ | |
try: | |
- f = open(repo.root + '/CONTRIBUTORS', 'r') | |
+ if contributorsURL is not None: | |
+ opening = contributorsURL | |
+ f = urllib2.urlopen(contributorsURL) | |
+ else: | |
+ opening = repo.root + '/CONTRIBUTORS' | |
+ f = open(repo.root + '/CONTRIBUTORS', 'r') | |
except: | |
- ui.write("warning: cannot open %s: %s\n" % (repo.root+'/CONTRI… | |
+ ui.write("warning: cannot open %s: %s\n" % (opening, Exception… | |
return | |
+ contributors = {} | |
for line in f: | |
# CONTRIBUTORS is a list of lines like: | |
# Person <email> | |
t@@ -980,6 +990,9 @@ def ReadContributors(ui, repo): | |
for extra in m.group(3).split(): | |
contributors[extra[1:-1].lower()] = (name, ema… | |
+ contributorsCache = contributors | |
+ return contributors | |
+ | |
def CheckContributor(ui, repo, user=None): | |
set_status("checking CONTRIBUTORS file") | |
user, userline = FindContributor(ui, repo, user, warn=False) | |
t@@ -997,6 +1010,7 @@ def FindContributor(ui, repo, user=None, warn=True): | |
if m: | |
user = m.group(1) | |
+ contributors = ReadContributors(ui, repo) | |
if user not in contributors: | |
if warn: | |
ui.warn("warning: cannot find %s in CONTRIBUTORS\n" % … | |
t@@ -1093,9 +1107,7 @@ def hg_matchPattern(ui, repo, *pats, **opts): | |
def hg_heads(ui, repo): | |
w = uiwrap(ui) | |
- ret = hg_commands.heads(ui, repo) | |
- if ret: | |
- raise hg_util.Abort(ret) | |
+ hg_commands.heads(ui, repo) | |
return w.output() | |
noise = [ | |
t@@ -1235,9 +1247,29 @@ def MatchAt(ctx, pats=None, opts=None, globbed=False, d… | |
####################################################################### | |
# Commands added by code review extension. | |
+# As of Mercurial 2.1 the commands are all required to return integer | |
+# exit codes, whereas earlier versions allowed returning arbitrary strings | |
+# to be printed as errors. We wrap the old functions to make sure we | |
+# always return integer exit codes now. Otherwise Mercurial dies | |
+# with a TypeError traceback (unsupported operand type(s) for &: 'str' and 'in… | |
+# Introduce a Python decorator to convert old functions to the new | |
+# stricter convention. | |
+ | |
+def hgcommand(f): | |
+ def wrapped(ui, repo, *pats, **opts): | |
+ err = f(ui, repo, *pats, **opts) | |
+ if type(err) is int: | |
+ return err | |
+ if not err: | |
+ return 0 | |
+ raise hg_util.Abort(err) | |
+ wrapped.__doc__ = f.__doc__ | |
+ return wrapped | |
+ | |
####################################################################### | |
# hg change | |
+@hgcommand | |
def change(ui, repo, *pats, **opts): | |
"""create, edit or delete a change list | |
t@@ -1278,7 +1310,7 @@ def change(ui, repo, *pats, **opts): | |
name = "new" | |
cl = CL("new") | |
if repo[None].branch() != "default": | |
- return "cannot create CL outside default branch" | |
+ return "cannot create CL outside default branch; switc… | |
dirty[cl] = True | |
files = ChangedFiles(ui, repo, pats, taken=Taken(ui, repo)) | |
t@@ -1351,6 +1383,7 @@ def change(ui, repo, *pats, **opts): | |
####################################################################### | |
# hg code-login (broken?) | |
+@hgcommand | |
def code_login(ui, repo, **opts): | |
"""log in to code review server | |
t@@ -1366,6 +1399,7 @@ def code_login(ui, repo, **opts): | |
# hg clpatch / undo / release-apply / download | |
# All concerned with applying or unapplying patches to the repository. | |
+@hgcommand | |
def clpatch(ui, repo, clname, **opts): | |
"""import a patch from the code review server | |
t@@ -1380,6 +1414,7 @@ def clpatch(ui, repo, clname, **opts): | |
return "cannot run hg clpatch outside default branch" | |
return clpatch_or_undo(ui, repo, clname, opts, mode="clpatch") | |
+@hgcommand | |
def undo(ui, repo, clname, **opts): | |
"""undo the effect of a CL | |
t@@ -1391,6 +1426,7 @@ def undo(ui, repo, clname, **opts): | |
return "cannot run hg undo outside default branch" | |
return clpatch_or_undo(ui, repo, clname, opts, mode="undo") | |
+@hgcommand | |
def release_apply(ui, repo, clname, **opts): | |
"""apply a CL to the release branch | |
t@@ -1560,7 +1596,7 @@ def clpatch_or_undo(ui, repo, clname, opts, mode): | |
try: | |
cmd = subprocess.Popen(argv, shell=False, stdin=subprocess.PIP… | |
except: | |
- return "hgpatch: " + ExceptionDetail() | |
+ return "hgpatch: " + ExceptionDetail() + "\nInstall hgpatch wi… | |
out, err = cmd.communicate(patch) | |
if cmd.returncode != 0 and not opts["ignore_hgpatch_failure"]: | |
t@@ -1643,6 +1679,7 @@ def lineDelta(deltas, n, len): | |
d = newdelta | |
return d, "" | |
+@hgcommand | |
def download(ui, repo, clname, **opts): | |
"""download a change from the code review server | |
t@@ -1662,6 +1699,7 @@ def download(ui, repo, clname, **opts): | |
####################################################################### | |
# hg file | |
+@hgcommand | |
def file(ui, repo, clname, pat, *pats, **opts): | |
"""assign files to or remove files from a change list | |
t@@ -1727,6 +1765,7 @@ def file(ui, repo, clname, pat, *pats, **opts): | |
####################################################################### | |
# hg gofmt | |
+@hgcommand | |
def gofmt(ui, repo, *pats, **opts): | |
"""apply gofmt to modified files | |
t@@ -1737,7 +1776,7 @@ def gofmt(ui, repo, *pats, **opts): | |
return codereview_disabled | |
files = ChangedExistingFiles(ui, repo, pats, opts) | |
- files = [f for f in files if f.endswith(".go")] | |
+ files = gofmt_required(files) | |
if not files: | |
return "no modified go files" | |
cwd = os.getcwd() | |
t@@ -1754,9 +1793,13 @@ def gofmt(ui, repo, *pats, **opts): | |
raise hg_util.Abort("gofmt: " + ExceptionDetail()) | |
return | |
+def gofmt_required(files): | |
+ return [f for f in files if (not f.startswith('test/') or f.startswith… | |
+ | |
####################################################################### | |
# hg mail | |
+@hgcommand | |
def mail(ui, repo, *pats, **opts): | |
"""mail a change for review | |
t@@ -1789,18 +1832,21 @@ def mail(ui, repo, *pats, **opts): | |
####################################################################### | |
# hg p / hg pq / hg ps / hg pending | |
+@hgcommand | |
def ps(ui, repo, *pats, **opts): | |
"""alias for hg p --short | |
""" | |
opts['short'] = True | |
return pending(ui, repo, *pats, **opts) | |
+@hgcommand | |
def pq(ui, repo, *pats, **opts): | |
"""alias for hg p --quick | |
""" | |
opts['quick'] = True | |
return pending(ui, repo, *pats, **opts) | |
+@hgcommand | |
def pending(ui, repo, *pats, **opts): | |
"""show pending changes | |
t@@ -1836,6 +1882,7 @@ def pending(ui, repo, *pats, **opts): | |
def need_sync(): | |
raise hg_util.Abort("local repository out of date; must sync before su… | |
+@hgcommand | |
def submit(ui, repo, *pats, **opts): | |
"""submit change to remote repository | |
t@@ -1915,7 +1962,7 @@ def submit(ui, repo, *pats, **opts): | |
# push to remote; if it fails for any reason, roll back | |
try: | |
new_heads = len(hg_heads(ui, repo).split()) | |
- if old_heads != new_heads: | |
+ if old_heads != new_heads and not (old_heads == 0 and new_head… | |
# Created new head, so we weren't up to date. | |
need_sync() | |
t@@ -1934,9 +1981,17 @@ def submit(ui, repo, *pats, **opts): | |
# We're committed. Upload final patch, close review, add commit messag… | |
changeURL = hg_node.short(node) | |
url = ui.expandpath("default") | |
- m = re.match("^https?://([^@/]+@)?([^.]+)\.googlecode\.com/hg/?", url) | |
+ m = re.match("(^https?://([^@/]+@)?([^.]+)\.googlecode\.com/hg/?)" + "… | |
+ "(^https?://([^@/]+@)?code\.google\.com/p/([^/.]+)(\.[^./]+)?/… | |
if m: | |
- changeURL = "http://code.google.com/p/%s/source/detail?r=%s" %… | |
+ if m.group(1): # prj.googlecode.com/hg/ case | |
+ changeURL = "http://code.google.com/p/%s/source/detail… | |
+ elif m.group(4) and m.group(7): # code.google.com/p/prj.subrep… | |
+ changeURL = "http://code.google.com/p/%s/source/detail… | |
+ elif m.group(4): # code.google.com/p/prj/ case | |
+ changeURL = "http://code.google.com/p/%s/source/detail… | |
+ else: | |
+ print >>sys.stderr, "URL: ", url | |
else: | |
print >>sys.stderr, "URL: ", url | |
pmsg = "*** Submitted as " + changeURL + " ***\n\n" + message | |
t@@ -1960,6 +2015,7 @@ def submit(ui, repo, *pats, **opts): | |
####################################################################### | |
# hg sync | |
+@hgcommand | |
def sync(ui, repo, **opts): | |
"""synchronize with remote repository | |
t@@ -2013,6 +2069,7 @@ def sync_changes(ui, repo): | |
####################################################################### | |
# hg upload | |
+@hgcommand | |
def upload(ui, repo, name, **opts): | |
"""upload diffs to the code review server | |
t@@ -2159,31 +2216,53 @@ def norollback(*pats, **opts): | |
"""(disabled when using this extension)""" | |
raise hg_util.Abort("codereview extension enabled; use undo instead of… | |
+codereview_init = False | |
+ | |
def reposetup(ui, repo): | |
global codereview_disabled | |
global defaultcc | |
+ # reposetup gets called both for the local repository | |
+ # and also for any repository we are pulling or pushing to. | |
+ # Only initialize the first time. | |
+ global codereview_init | |
+ if codereview_init: | |
+ return | |
+ codereview_init = True | |
+ | |
+ # Read repository-specific options from lib/codereview/codereview.cfg … | |
+ root = '' | |
+ try: | |
+ root = repo.root | |
+ except: | |
+ # Yes, repo might not have root; see issue 959. | |
+ codereview_disabled = 'codereview disabled: repository has no … | |
+ return | |
+ | |
repo_config_path = '' | |
- # Read repository-specific options from lib/codereview/codereview.cfg | |
+ p1 = root + '/lib/codereview/codereview.cfg' | |
+ p2 = root + '/codereview.cfg' | |
+ if os.access(p1, os.F_OK): | |
+ repo_config_path = p1 | |
+ else: | |
+ repo_config_path = p2 | |
try: | |
- repo_config_path = repo.root + '/lib/codereview/codereview.cfg' | |
f = open(repo_config_path) | |
for line in f: | |
- if line.startswith('defaultcc: '): | |
- defaultcc = SplitCommaSpace(line[10:]) | |
+ if line.startswith('defaultcc:'): | |
+ defaultcc = SplitCommaSpace(line[len('defaultc… | |
+ if line.startswith('contributors:'): | |
+ global contributorsURL | |
+ contributorsURL = line[len('contributors:'):].… | |
except: | |
- # If there are no options, chances are good this is not | |
- # a code review repository; stop now before we foul | |
- # things up even worse. Might also be that repo doesn't | |
- # even have a root. See issue 959. | |
- if repo_config_path == '': | |
- codereview_disabled = 'codereview disabled: repository… | |
- else: | |
- codereview_disabled = 'codereview disabled: cannot ope… | |
+ codereview_disabled = 'codereview disabled: cannot open ' + re… | |
return | |
+ remote = ui.config("paths", "default", "") | |
+ if remote.find("://") < 0: | |
+ raise hg_util.Abort("codereview: default path '%s' is not a UR… | |
+ | |
InstallMatch(ui, repo) | |
- ReadContributors(ui, repo) | |
RietveldSetup(ui, repo) | |
# Disable the Mercurial commands that might change the repository. | |
t@@ -2531,15 +2610,14 @@ def RietveldSetup(ui, repo): | |
global releaseBranch | |
tags = repo.branchtags().keys() | |
- if 'release-branch.r100' in tags: | |
+ if 'release-branch.go10' in tags: | |
# NOTE(rsc): This tags.sort is going to get the wrong | |
- # answer when comparing release-branch.r99 with | |
- # release-branch.r100. If we do ten releases a year | |
- # that gives us 4 years before we have to worry about this. | |
- raise hg_util.Abort('tags.sort needs to be fixed for release-b… | |
+ # answer when comparing release-branch.go9 with | |
+ # release-branch.go10. It will be a while before we care. | |
+ raise hg_util.Abort('tags.sort needs to be fixed for release-b… | |
tags.sort() | |
for t in tags: | |
- if t.startswith('release-branch.'): | |
+ if t.startswith('release-branch.go'): | |
releaseBranch = t | |
####################################################################### | |
t@@ -3265,6 +3343,10 @@ class FakeMercurialUI(object): | |
return self | |
def status(self, *args, **opts): | |
pass | |
+ | |
+ def formatter(self, topic, opts): | |
+ from mercurial.formatter import plainformatter | |
+ return plainformatter(self, topic, opts) | |
def readconfig(self, *args, **opts): | |
pass | |
t@@ -3298,7 +3380,11 @@ class MercurialVCS(VersionControlSystem): | |
if not err and mqparent != "": | |
self.base_rev = mqparent | |
else: | |
- self.base_rev = RunShell(["hg", "parents", "-q… | |
+ out = RunShell(["hg", "parents", "-q"], silent… | |
+ if not out: | |
+ # No revisions; use 0 to mean a reposi… | |
+ out = "0:0" | |
+ self.base_rev = out.split(':')[1].strip() | |
def _GetRelPath(self, filename): | |
"""Get relative path of a file according to the current direct… | |
given its logical path in the repo.""" |