diff options
author | David Symonds <dsymonds@golang.org> | 2014-12-08 18:47:42 +1100 |
---|---|---|
committer | David Symonds <dsymonds@golang.org> | 2014-12-08 07:51:54 +0000 |
commit | 439b32936367c3efd0dadab48dd51202e1a510f1 (patch) | |
tree | 497dd40a0fefb8d5758e327c79acc691e607af7d /lib | |
parent | 3aaef230face573c1eb94146370a72cbff2f0d58 (diff) | |
download | go-439b32936367c3efd0dadab48dd51202e1a510f1.tar.gz go-439b32936367c3efd0dadab48dd51202e1a510f1.zip |
remove the obsolete lib/codereview.
Change-Id: Idd2e74053ce4a2be3d757aef1bb83313973de6f0
Reviewed-on: https://go-review.googlesource.com/1175
Reviewed-by: Andrew Gerrand <adg@golang.org>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/codereview/codereview.cfg | 2 | ||||
-rw-r--r-- | lib/codereview/codereview.py | 3694 | ||||
-rwxr-xr-x | lib/codereview/test.sh | 199 |
3 files changed, 0 insertions, 3895 deletions
diff --git a/lib/codereview/codereview.cfg b/lib/codereview/codereview.cfg deleted file mode 100644 index 43dbf3ce3b8..00000000000 --- a/lib/codereview/codereview.cfg +++ /dev/null @@ -1,2 +0,0 @@ -defaultcc: golang-codereviews@googlegroups.com -contributors: http://go.googlecode.com/hg/CONTRIBUTORS diff --git a/lib/codereview/codereview.py b/lib/codereview/codereview.py deleted file mode 100644 index 0c9b27a318e..00000000000 --- a/lib/codereview/codereview.py +++ /dev/null @@ -1,3694 +0,0 @@ -# coding=utf-8 -# (The line above is necessary so that I can use 世界 in the -# *comment* below without Python getting all bent out of shape.) - -# Copyright 2007-2009 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''Mercurial interface to codereview.appspot.com. - -To configure, set the following options in -your repository's .hg/hgrc file. - - [extensions] - codereview = /path/to/codereview.py - - [codereview] - server = codereview.appspot.com - -The server should be running Rietveld; see http://code.google.com/p/rietveld/. - -In addition to the new commands, this extension introduces -the file pattern syntax @nnnnnn, where nnnnnn is a change list -number, to mean the files included in that change list, which -must be associated with the current client. - -For example, if change 123456 contains the files x.go and y.go, -"hg diff @123456" is equivalent to"hg diff x.go y.go". -''' - -import sys - -if __name__ == "__main__": - print >>sys.stderr, "This is a Mercurial extension and should not be invoked directly." - sys.exit(2) - -# We require Python 2.6 for the json package. -if sys.version < '2.6': - print >>sys.stderr, "The codereview extension requires Python 2.6 or newer." - print >>sys.stderr, "You are running Python " + sys.version - sys.exit(2) - -import json -import os -import re -import stat -import subprocess -import threading -import time - -from mercurial import commands as hg_commands -from mercurial import util as hg_util - -# bind Plan 9 preferred dotfile location -if os.sys.platform == 'plan9': - try: - import plan9 - n = plan9.bind(os.path.expanduser("~/lib"), os.path.expanduser("~"), plan9.MBEFORE|plan9.MCREATE) - except ImportError: - pass - -defaultcc = None -codereview_disabled = None -real_rollback = None -releaseBranch = None -server = "codereview.appspot.com" -server_url_base = None -testing = None - -####################################################################### -# Normally I would split this into multiple files, but it simplifies -# import path headaches to keep it all in one file. Sorry. -# The different parts of the file are separated by banners like this one. - -####################################################################### -# Helpers - -def RelativePath(path, cwd): - n = len(cwd) - if path.startswith(cwd) and path[n] == '/': - return path[n+1:] - return path - -def Sub(l1, l2): - return [l for l in l1 if l not in l2] - -def Add(l1, l2): - l = l1 + Sub(l2, l1) - l.sort() - return l - -def Intersect(l1, l2): - return [l for l in l1 if l in l2] - -####################################################################### -# RE: UNICODE STRING HANDLING -# -# Python distinguishes between the str (string of bytes) -# and unicode (string of code points) types. Most operations -# work on either one just fine, but some (like regexp matching) -# require unicode, and others (like write) require str. -# -# As befits the language, Python hides the distinction between -# unicode and str by converting between them silently, but -# *only* if all the bytes/code points involved are 7-bit ASCII. -# This means that if you're not careful, your program works -# fine on "hello, world" and fails on "hello, 世界". And of course, -# the obvious way to be careful - use static types - is unavailable. -# So the only way is trial and error to find where to put explicit -# conversions. -# -# Because more functions do implicit conversion to str (string of bytes) -# than do implicit conversion to unicode (string of code points), -# the convention in this module is to represent all text as str, -# converting to unicode only when calling a unicode-only function -# and then converting back to str as soon as possible. - -def typecheck(s, t): - if type(s) != t: - raise hg_util.Abort("type check failed: %s has type %s != %s" % (repr(s), type(s), t)) - -# If we have to pass unicode instead of str, ustr does that conversion clearly. -def ustr(s): - typecheck(s, str) - return s.decode("utf-8") - -# Even with those, Mercurial still sometimes turns unicode into str -# and then tries to use it as ascii. Change Mercurial's default. -def set_mercurial_encoding_to_utf8(): - from mercurial import encoding - encoding.encoding = 'utf-8' - -set_mercurial_encoding_to_utf8() - -# Even with those we still run into problems. -# I tried to do things by the book but could not convince -# Mercurial to let me check in a change with UTF-8 in the -# CL description or author field, no matter how many conversions -# between str and unicode I inserted and despite changing the -# default encoding. I'm tired of this game, so set the default -# encoding for all of Python to 'utf-8', not 'ascii'. -def default_to_utf8(): - import sys - stdout, __stdout__ = sys.stdout, sys.__stdout__ - reload(sys) # site.py deleted setdefaultencoding; get it back - sys.stdout, sys.__stdout__ = stdout, __stdout__ - sys.setdefaultencoding('utf-8') - -default_to_utf8() - -####################################################################### -# Status printer for long-running commands - -global_status = None - -def set_status(s): - if verbosity > 0: - print >>sys.stderr, time.asctime(), s - global global_status - global_status = s - -class StatusThread(threading.Thread): - def __init__(self): - threading.Thread.__init__(self) - def run(self): - # pause a reasonable amount of time before - # starting to display status messages, so that - # most hg commands won't ever see them. - time.sleep(30) - - # now show status every 15 seconds - while True: - time.sleep(15 - time.time() % 15) - s = global_status - if s is None: - continue - if s == "": - s = "(unknown status)" - print >>sys.stderr, time.asctime(), s - -def start_status_thread(): - t = StatusThread() - t.setDaemon(True) # allowed to exit if t is still running - t.start() - -####################################################################### -# Change list parsing. -# -# Change lists are stored in .hg/codereview/cl.nnnnnn -# where nnnnnn is the number assigned by the code review server. -# Most data about a change list is stored on the code review server -# too: the description, reviewer, and cc list are all stored there. -# The only thing in the cl.nnnnnn file is the list of relevant files. -# Also, the existence of the cl.nnnnnn file marks this repository -# as the one where the change list lives. - -emptydiff = """Index: ~rietveld~placeholder~ -=================================================================== -diff --git a/~rietveld~placeholder~ b/~rietveld~placeholder~ -new file mode 100644 -""" - -class CL(object): - def __init__(self, name): - typecheck(name, str) - self.name = name - self.desc = '' - self.files = [] - self.reviewer = [] - self.cc = [] - self.url = '' - self.local = False - self.web = False - self.copied_from = None # None means current user - self.mailed = False - self.private = False - self.lgtm = [] - - def DiskText(self): - cl = self - s = "" - if cl.copied_from: - s += "Author: " + cl.copied_from + "\n\n" - if cl.private: - s += "Private: " + str(self.private) + "\n" - s += "Mailed: " + str(self.mailed) + "\n" - s += "Description:\n" - s += Indent(cl.desc, "\t") - s += "Files:\n" - for f in cl.files: - s += "\t" + f + "\n" - typecheck(s, str) - return s - - def EditorText(self): - cl = self - s = _change_prolog - s += "\n" - if cl.copied_from: - s += "Author: " + cl.copied_from + "\n" - if cl.url != '': - s += 'URL: ' + cl.url + ' # cannot edit\n\n' - if cl.private: - s += "Private: True\n" - s += "Reviewer: " + JoinComma(cl.reviewer) + "\n" - s += "CC: " + JoinComma(cl.cc) + "\n" - s += "\n" - s += "Description:\n" - if cl.desc == '': - s += "\t<enter description here>\n" - else: - s += Indent(cl.desc, "\t") - s += "\n" - if cl.local or cl.name == "new": - s += "Files:\n" - for f in cl.files: - s += "\t" + f + "\n" - s += "\n" - typecheck(s, str) - return s - - def PendingText(self, quick=False): - cl = self - s = cl.name + ":" + "\n" - s += Indent(cl.desc, "\t") - s += "\n" - if cl.copied_from: - s += "\tAuthor: " + cl.copied_from + "\n" - if not quick: - s += "\tReviewer: " + JoinComma(cl.reviewer) + "\n" - for (who, line, _) in cl.lgtm: - s += "\t\t" + who + ": " + line + "\n" - s += "\tCC: " + JoinComma(cl.cc) + "\n" - s += "\tFiles:\n" - for f in cl.files: - s += "\t\t" + f + "\n" - typecheck(s, str) - return s - - def Flush(self, ui, repo): - if self.name == "new": - self.Upload(ui, repo, gofmt_just_warn=True, creating=True) - dir = CodeReviewDir(ui, repo) - path = dir + '/cl.' + self.name - f = open(path+'!', "w") - f.write(self.DiskText()) - f.close() - if sys.platform == "win32" and os.path.isfile(path): - os.remove(path) - os.rename(path+'!', path) - if self.web and not self.copied_from: - EditDesc(self.name, desc=self.desc, - reviewers=JoinComma(self.reviewer), cc=JoinComma(self.cc), - private=self.private) - - def Delete(self, ui, repo): - dir = CodeReviewDir(ui, repo) - os.unlink(dir + "/cl." + self.name) - - def Subject(self, ui, repo): - s = line1(self.desc) - if len(s) > 60: - s = s[0:55] + "..." - if self.name != "new": - s = "code review %s: %s" % (self.name, s) - typecheck(s, str) - return branch_prefix(ui, repo) + s - - def Upload(self, ui, repo, send_mail=False, gofmt=True, gofmt_just_warn=False, creating=False, quiet=False): - if not self.files and not creating: - ui.warn("no files in change list\n") - if ui.configbool("codereview", "force_gofmt", True) and gofmt: - CheckFormat(ui, repo, self.files, just_warn=gofmt_just_warn) - set_status("uploading CL metadata + diffs") - os.chdir(repo.root) - - form_fields = [ - ("content_upload", "1"), - ("reviewers", JoinComma(self.reviewer)), - ("cc", JoinComma(self.cc)), - ("description", self.desc), - ("base_hashes", ""), - ] - - if self.name != "new": - form_fields.append(("issue", self.name)) - vcs = None - # We do not include files when creating the issue, - # because we want the patch sets to record the repository - # and base revision they are diffs against. We use the patch - # set message for that purpose, but there is no message with - # the first patch set. Instead the message gets used as the - # new CL's overall subject. So omit the diffs when creating - # and then we'll run an immediate upload. - # This has the effect that every CL begins with an empty "Patch set 1". - if self.files and not creating: - vcs = MercurialVCS(upload_options, ui, repo) - data = vcs.GenerateDiff(self.files) - files = vcs.GetBaseFiles(data) - if len(data) > MAX_UPLOAD_SIZE: - uploaded_diff_file = [] - form_fields.append(("separate_patches", "1")) - else: - uploaded_diff_file = [("data", "data.diff", data)] - else: - uploaded_diff_file = [("data", "data.diff", emptydiff)] - - if vcs and self.name != "new": - form_fields.append(("subject", "diff -r " + vcs.base_rev + " " + ui.expandpath("default"))) - else: - # First upload sets the subject for the CL itself. - form_fields.append(("subject", self.Subject(ui, repo))) - - ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file) - response_body = MySend("/upload", body, content_type=ctype) - patchset = None - msg = response_body - lines = msg.splitlines() - if len(lines) >= 2: - msg = lines[0] - patchset = lines[1].strip() - patches = [x.split(" ", 1) for x in lines[2:]] - else: - print >>sys.stderr, "Server says there is nothing to upload (probably wrong):\n" + msg - if response_body.startswith("Issue updated.") and quiet: - pass - else: - ui.status(msg + "\n") - set_status("uploaded CL metadata + diffs") - if not response_body.startswith("Issue created.") and not response_body.startswith("Issue updated."): - raise hg_util.Abort("failed to update issue: " + response_body) - issue = msg[msg.rfind("/")+1:] - self.name = issue - if not self.url: - self.url = server_url_base + self.name - if not uploaded_diff_file: - set_status("uploading patches") - patches = UploadSeparatePatches(issue, rpc, patchset, data, upload_options) - if vcs: - set_status("uploading base files") - vcs.UploadBaseFiles(issue, rpc, patches, patchset, upload_options, files) - if patchset != "1": - MySend("/" + issue + "/upload_complete/" + patchset, payload="") - if send_mail: - set_status("sending mail") - MySend("/" + issue + "/mail", payload="") - self.web = True - set_status("flushing changes to disk") - self.Flush(ui, repo) - return - - def Mail(self, ui, repo): - pmsg = "Hello " + JoinComma(self.reviewer) - if self.cc: - pmsg += " (cc: %s)" % (', '.join(self.cc),) - pmsg += ",\n" - pmsg += "\n" - repourl = ui.expandpath("default") - if not self.mailed: - pmsg += "I'd like you to review this change to" - branch = repo[None].branch() - if branch.startswith("dev."): - pmsg += " the " + branch + " branch of" - pmsg += "\n" + repourl + "\n" - else: - pmsg += "Please take another look.\n" - typecheck(pmsg, str) - PostMessage(ui, self.name, pmsg, subject=self.Subject(ui, repo)) - self.mailed = True - self.Flush(ui, repo) - -def GoodCLName(name): - typecheck(name, str) - return re.match("^[0-9]+$", name) - -def ParseCL(text, name): - typecheck(text, str) - typecheck(name, str) - sname = None - lineno = 0 - sections = { - 'Author': '', - 'Description': '', - 'Files': '', - 'URL': '', - 'Reviewer': '', - 'CC': '', - 'Mailed': '', - 'Private': '', - } - for line in text.split('\n'): - lineno += 1 - line = line.rstrip() - if line != '' and line[0] == '#': - continue - if line == '' or line[0] == ' ' or line[0] == '\t': - if sname == None and line != '': - return None, lineno, 'text outside section' - if sname != None: - sections[sname] += line + '\n' - continue - p = line.find(':') - if p >= 0: - s, val = line[:p].strip(), line[p+1:].strip() - if s in sections: - sname = s - if val != '': - sections[sname] += val + '\n' - continue - return None, lineno, 'malformed section header' - - for k in sections: - sections[k] = StripCommon(sections[k]).rstrip() - - cl = CL(name) - if sections['Author']: - cl.copied_from = sections['Author'] - cl.desc = sections['Description'] - for line in sections['Files'].split('\n'): - i = line.find('#') - if i >= 0: - line = line[0:i].rstrip() - line = line.strip() - if line == '': - continue - cl.files.append(line) - cl.reviewer = SplitCommaSpace(sections['Reviewer']) - cl.cc = SplitCommaSpace(sections['CC']) - cl.url = sections['URL'] - if sections['Mailed'] != 'False': - # Odd default, but avoids spurious mailings when - # reading old CLs that do not have a Mailed: line. - # CLs created with this update will always have - # Mailed: False on disk. - cl.mailed = True - if sections['Private'] in ('True', 'true', 'Yes', 'yes'): - cl.private = True - if cl.desc == '<enter description here>': - cl.desc = '' - return cl, 0, '' - -def SplitCommaSpace(s): - typecheck(s, str) - s = s.strip() - if s == "": - return [] - return re.split(", *", s) - -def CutDomain(s): - typecheck(s, str) - i = s.find('@') - if i >= 0: - s = s[0:i] - return s - -def JoinComma(l): - seen = {} - uniq = [] - for s in l: - typecheck(s, str) - if s not in seen: - seen[s] = True - uniq.append(s) - - return ", ".join(uniq) - -def ExceptionDetail(): - s = str(sys.exc_info()[0]) - if s.startswith("<type '") and s.endswith("'>"): - s = s[7:-2] - elif s.startswith("<class '") and s.endswith("'>"): - s = s[8:-2] - arg = str(sys.exc_info()[1]) - if len(arg) > 0: - s += ": " + arg - return s - -def IsLocalCL(ui, repo, name): - return GoodCLName(name) and os.access(CodeReviewDir(ui, repo) + "/cl." + name, 0) - -# Load CL from disk and/or the web. -def LoadCL(ui, repo, name, web=True): - typecheck(name, str) - set_status("loading CL " + name) - if not GoodCLName(name): - return None, "invalid CL name" - dir = CodeReviewDir(ui, repo) - path = dir + "cl." + name - if os.access(path, 0): - ff = open(path) - text = ff.read() - ff.close() - cl, lineno, err = ParseCL(text, name) - if err != "": - return None, "malformed CL data: "+err - cl.local = True - else: - cl = CL(name) - if web: - set_status("getting issue metadata from web") - d = JSONGet(ui, "/api/" + name + "?messages=true") - set_status(None) - if d is None: - return None, "cannot load CL %s from server" % (name,) - if 'owner_email' not in d or 'issue' not in d or str(d['issue']) != name: - return None, "malformed response loading CL data from code review server" - cl.dict = d - cl.reviewer = d.get('reviewers', []) - cl.cc = d.get('cc', []) - if cl.local and cl.copied_from and cl.desc: - # local copy of CL written by someone else - # and we saved a description. use that one, - # so that committers can edit the description - # before doing hg submit. - pass - else: - cl.desc = d.get('description', "") - cl.url = server_url_base + name - cl.web = True - cl.private = d.get('private', False) != False - cl.lgtm = [] - for m in d.get('messages', []): - if m.get('approval', False) == True or m.get('disapproval', False) == True: - who = re.sub('@.*', '', m.get('sender', '')) - text = re.sub("\n(.|\n)*", '', m.get('text', '')) - cl.lgtm.append((who, text, m.get('approval', False))) - - set_status("loaded CL " + name) - return cl, '' - -class LoadCLThread(threading.Thread): - def __init__(self, ui, repo, dir, f, web): - threading.Thread.__init__(self) - self.ui = ui - self.repo = repo - self.dir = dir - self.f = f - self.web = web - self.cl = None - def run(self): - cl, err = LoadCL(self.ui, self.repo, self.f[3:], web=self.web) - if err != '': - self.ui.warn("loading "+self.dir+self.f+": " + err + "\n") - return - self.cl = cl - -# Load all the CLs from this repository. -def LoadAllCL(ui, repo, web=True): - dir = CodeReviewDir(ui, repo) - m = {} - files = [f for f in os.listdir(dir) if f.startswith('cl.')] - if not files: - return m - active = [] - first = True - for f in files: - t = LoadCLThread(ui, repo, dir, f, web) - t.start() - if web and first: - # first request: wait in case it needs to authenticate - # otherwise we get lots of user/password prompts - # running in parallel. - t.join() - if t.cl: - m[t.cl.name] = t.cl - first = False - else: - active.append(t) - for t in active: - t.join() - if t.cl: - m[t.cl.name] = t.cl - return m - -# Find repository root. On error, ui.warn and return None -def RepoDir(ui, repo): - url = repo.url(); - if not url.startswith('file:'): - ui.warn("repository %s is not in local file system\n" % (url,)) - return None - url = url[5:] - if url.endswith('/'): - url = url[:-1] - typecheck(url, str) - return url - -# Find (or make) code review directory. On error, ui.warn and return None -def CodeReviewDir(ui, repo): - dir = RepoDir(ui, repo) - if dir == None: - return None - dir += '/.hg/codereview/' - if not os.path.isdir(dir): - try: - os.mkdir(dir, 0700) - except: - ui.warn('cannot mkdir %s: %s\n' % (dir, ExceptionDetail())) - return None - typecheck(dir, str) - return dir - -# Turn leading tabs into spaces, so that the common white space -# prefix doesn't get confused when people's editors write out -# some lines with spaces, some with tabs. Only a heuristic -# (some editors don't use 8 spaces either) but a useful one. -def TabsToSpaces(line): - i = 0 - while i < len(line) and line[i] == '\t': - i += 1 - return ' '*(8*i) + line[i:] - -# Strip maximal common leading white space prefix from text -def StripCommon(text): - typecheck(text, str) - ws = None - for line in text.split('\n'): - line = line.rstrip() - if line == '': - continue - line = TabsToSpaces(line) - white = line[:len(line)-len(line.lstrip())] - if ws == None: - ws = white - else: - common = '' - for i in range(min(len(white), len(ws))+1): - if white[0:i] == ws[0:i]: - common = white[0:i] - ws = common - if ws == '': - break - if ws == None: - return text - t = '' - for line in text.split('\n'): - line = line.rstrip() - line = TabsToSpaces(line) - if line.startswith(ws): - line = line[len(ws):] - if line == '' and t == '': - continue - t += line + '\n' - while len(t) >= 2 and t[-2:] == '\n\n': - t = t[:-1] - typecheck(t, str) - return t - -# Indent text with indent. -def Indent(text, indent): - typecheck(text, str) - typecheck(indent, str) - t = '' - for line in text.split('\n'): - t += indent + line + '\n' - typecheck(t, str) - return t - -# Return the first line of l -def line1(text): - typecheck(text, str) - return text.split('\n')[0] - -_change_prolog = """# Change list. -# Lines beginning with # are ignored. -# Multi-line values should be indented. -""" - -desc_re = '^(.+: |(tag )?(release|weekly)\.|fix build|undo CL)' - -desc_msg = '''Your CL description appears not to use the standard form. - -The first line of your change description is conventionally a -one-line summary of the change, prefixed by the primary affected package, -and is used as the subject for code review mail; the rest of the description -elaborates. - -Examples: - - encoding/rot13: new package - - math: add IsInf, IsNaN - - net: fix cname in LookupHost - - unicode: update to Unicode 5.0.2 - -''' - -def promptyesno(ui, msg): - if hgversion >= "2.7": - return ui.promptchoice(msg + " $$ &yes $$ &no", 0) == 0 - else: - return ui.promptchoice(msg, ["&yes", "&no"], 0) == 0 - -def promptremove(ui, repo, f): - if promptyesno(ui, "hg remove %s (y/n)?" % (f,)): - if hg_commands.remove(ui, repo, 'path:'+f) != 0: - ui.warn("error removing %s" % (f,)) - -def promptadd(ui, repo, f): - if promptyesno(ui, "hg add %s (y/n)?" % (f,)): - if hg_commands.add(ui, repo, 'path:'+f) != 0: - ui.warn("error adding %s" % (f,)) - -def EditCL(ui, repo, cl): - set_status(None) # do not show status - s = cl.EditorText() - while True: - s = ui.edit(s, ui.username()) - - # We can't trust Mercurial + Python not to die before making the change, - # so, by popular demand, just scribble the most recent CL edit into - # $(hg root)/last-change so that if Mercurial does die, people - # can look there for their work. - try: - f = open(repo.root+"/last-change", "w") - f.write(s) - f.close() - except: - pass - - clx, line, err = ParseCL(s, cl.name) - if err != '': - if not promptyesno(ui, "error parsing change list: line %d: %s\nre-edit (y/n)?" % (line, err)): - return "change list not modified" - continue - - # Check description. - if clx.desc == '': - if promptyesno(ui, "change list should have a description\nre-edit (y/n)?"): - continue - elif re.search('<enter reason for undo>', clx.desc): - if promptyesno(ui, "change list description omits reason for undo\nre-edit (y/n)?"): - continue - elif not re.match(desc_re, clx.desc.split('\n')[0]): - if promptyesno(ui, desc_msg + "re-edit (y/n)?"): - continue - - # Check file list for files that need to be hg added or hg removed - # or simply aren't understood. - pats = ['path:'+f for f in clx.files] - changed = hg_matchPattern(ui, repo, *pats, modified=True, added=True, removed=True) - deleted = hg_matchPattern(ui, repo, *pats, deleted=True) - unknown = hg_matchPattern(ui, repo, *pats, unknown=True) - ignored = hg_matchPattern(ui, repo, *pats, ignored=True) - clean = hg_matchPattern(ui, repo, *pats, clean=True) - files = [] - for f in clx.files: - if f in changed: - files.append(f) - continue - if f in deleted: - promptremove(ui, repo, f) - files.append(f) - continue - if f in unknown: - promptadd(ui, repo, f) - files.append(f) - continue - if f in ignored: - ui.warn("error: %s is excluded by .hgignore; omitting\n" % (f,)) - continue - if f in clean: - ui.warn("warning: %s is listed in the CL but unchanged\n" % (f,)) - files.append(f) - continue - p = repo.root + '/' + f - if os.path.isfile(p): - ui.warn("warning: %s is a file but not known to hg\n" % (f,)) - files.append(f) - continue - if os.path.isdir(p): - ui.warn("error: %s is a directory, not a file; omitting\n" % (f,)) - continue - ui.warn("error: %s does not exist; omitting\n" % (f,)) - clx.files = files - - cl.desc = clx.desc - cl.reviewer = clx.reviewer - cl.cc = clx.cc - cl.files = clx.files - cl.private = clx.private - break - return "" - -# For use by submit, etc. (NOT by change) -# Get change list number or list of files from command line. -# If files are given, make a new change list. -def CommandLineCL(ui, repo, pats, opts, op="verb", defaultcc=None): - if len(pats) > 0 and GoodCLName(pats[0]): - if len(pats) != 1: - return None, "cannot specify change number and file names" - if opts.get('message'): - return None, "cannot use -m with existing CL" - cl, err = LoadCL(ui, repo, pats[0], web=True) - if err != "": - return None, err - else: - cl = CL("new") - cl.local = True - cl.files = ChangedFiles(ui, repo, pats, taken=Taken(ui, repo)) - if not cl.files: - return None, "no files changed (use hg %s <number> to use existing CL)" % op - if opts.get('reviewer'): - cl.reviewer = Add(cl.reviewer, SplitCommaSpace(opts.get('reviewer'))) - if opts.get('cc'): - cl.cc = Add(cl.cc, SplitCommaSpace(opts.get('cc'))) - if defaultcc and not cl.private: - cl.cc = Add(cl.cc, defaultcc) - if cl.name == "new": - if opts.get('message'): - cl.desc = opts.get('message') - else: - err = EditCL(ui, repo, cl) - if err != '': - return None, err - return cl, "" - -####################################################################### -# Change list file management - -# Return list of changed files in repository that match pats. -# The patterns came from the command line, so we warn -# if they have no effect or cannot be understood. -def ChangedFiles(ui, repo, pats, taken=None): - taken = taken or {} - # Run each pattern separately so that we can warn about - # patterns that didn't do anything useful. - for p in pats: - for f in hg_matchPattern(ui, repo, p, unknown=True): - promptadd(ui, repo, f) - for f in hg_matchPattern(ui, repo, p, removed=True): - promptremove(ui, repo, f) - files = hg_matchPattern(ui, repo, p, modified=True, added=True, removed=True) - for f in files: - if f in taken: - ui.warn("warning: %s already in CL %s\n" % (f, taken[f].name)) - if not files: - ui.warn("warning: %s did not match any modified files\n" % (p,)) - - # Again, all at once (eliminates duplicates) - l = hg_matchPattern(ui, repo, *pats, modified=True, added=True, removed=True) - l.sort() - if taken: - l = Sub(l, taken.keys()) - return l - -# Return list of changed files in repository that match pats and still exist. -def ChangedExistingFiles(ui, repo, pats, opts): - l = hg_matchPattern(ui, repo, *pats, modified=True, added=True) - l.sort() - return l - -# Return list of files claimed by existing CLs -def Taken(ui, repo): - all = LoadAllCL(ui, repo, web=False) - taken = {} - for _, cl in all.items(): - for f in cl.files: - taken[f] = cl - return taken - -# Return list of changed files that are not claimed by other CLs -def DefaultFiles(ui, repo, pats): - return ChangedFiles(ui, repo, pats, taken=Taken(ui, repo)) - -####################################################################### -# File format checking. - -def CheckFormat(ui, repo, files, just_warn=False): - set_status("running gofmt") - CheckGofmt(ui, repo, files, just_warn) - CheckTabfmt(ui, repo, files, just_warn) - -# Check that gofmt run on the list of files does not change them -def CheckGofmt(ui, repo, files, just_warn): - files = gofmt_required(files) - if not files: - return - cwd = os.getcwd() - files = [RelativePath(repo.root + '/' + f, cwd) for f in files] - files = [f for f in files if os.access(f, 0)] - if not files: - return - try: - cmd = subprocess.Popen(["gofmt", "-l"] + files, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=sys.platform != "win32") - cmd.stdin.close() - except: - raise hg_util.Abort("gofmt: " + ExceptionDetail()) - data = cmd.stdout.read() - errors = cmd.stderr.read() - cmd.wait() - set_status("done with gofmt") - if len(errors) > 0: - ui.warn("gofmt errors:\n" + errors.rstrip() + "\n") - return - if len(data) > 0: - msg = "gofmt needs to format these files (run hg gofmt):\n" + Indent(data, "\t").rstrip() - if just_warn: - ui.warn("warning: " + msg + "\n") - else: - raise hg_util.Abort(msg) - return - -# 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"\.[chys]$", f) and not re.search(r"\.tab\.[ch]$", f)] - if not files: - return - cwd = os.getcwd() - files = [RelativePath(repo.root + '/' + f, cwd) for f in files] - files = [f for f in files if os.access(f, 0)] - badfiles = [] - for f in files: - try: - for line in open(f, 'r'): - # Four leading spaces is enough to complain about, - # except that some Plan 9 code uses four spaces as the label indent, - # so allow that. - if line.startswith(' ') and not re.match(' [A-Za-z0-9_]+:', line): - badfiles.append(f) - break - except: - # ignore cannot open file, etc. - pass - if len(badfiles) > 0: - msg = "these files use spaces for indentation (use tabs instead):\n\t" + "\n\t".join(badfiles) - if just_warn: - ui.warn("warning: " + msg + "\n") - else: - raise hg_util.Abort(msg) - return - -####################################################################### -# CONTRIBUTORS file parsing - -contributorsCache = None -contributorsURL = None - -def ReadContributors(ui, repo): - global contributorsCache - if contributorsCache is not None: - return contributorsCache - - try: - 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" % (opening, ExceptionDetail())) - return {} - - contributors = {} - for line in f: - # CONTRIBUTORS is a list of lines like: - # Person <email> - # Person <email> <alt-email> - # The first email address is the one used in commit logs. - if line.startswith('#'): - continue - m = re.match(r"([^<>]+\S)\s+(<[^<>\s]+>)((\s+<[^<>\s]+>)*)\s*$", line) - if m: - name = m.group(1) - email = m.group(2)[1:-1] - contributors[email.lower()] = (name, email) - for extra in m.group( |