tcodereview: update from go - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 338bd4bb79e79b3df1aaed0ed9d78d3989f4c1f7
 (DIR) parent db60da46b3255502597124a870d7b8714d659749
 (HTM) Author: Russ Cox <rsc@swtch.com>
       Date:   Tue,  8 Nov 2011 09:03:05 -0500
       
       codereview: update from go
       
       R=rsc
       http://codereview.appspot.com/5340059
       
       Diffstat:
         M lib/codereview/codereview.py        |     171 +++++++++++++++++++++++--------
       
       1 file changed, 127 insertions(+), 44 deletions(-)
       ---
 (DIR) diff --git a/lib/codereview/codereview.py b/lib/codereview/codereview.py
       t@@ -38,7 +38,7 @@ 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".
        '''
        
       -from mercurial import cmdutil, commands, hg, util, error, match
       +from mercurial import cmdutil, commands, hg, util, error, match, discovery
        from mercurial.node import nullrev, hex, nullid, short
        import os, re, time
        import stat
       t@@ -69,11 +69,11 @@ except:
                from mercurial.version import version as v
                hgversion = v.get_version()
        
       -try:
       -        from mercurial.discovery import findcommonincoming
       -except:
       -        def findcommonincoming(repo, remote):
       -                return repo.findcommonincoming(remote)
       +# in Mercurial 1.9 the cmdutil.match and cmdutil.revpair moved to scmutil
       +if hgversion >= '1.9':
       +    from mercurial import scmutil
       +else:
       +    scmutil = cmdutil
        
        oldMessage = """
        The code review extension requires Mercurial 1.3 or newer.
       t@@ -107,6 +107,22 @@ def promptyesno(ui, msg):
                except AttributeError:
                        return ui.prompt(msg, ["&yes", "&no"], "y") != "n"
        
       +def incoming(repo, other):
       +        fui = FakeMercurialUI()
       +        ret = commands.incoming(fui, repo, *[other.path], **{'bundle': '', 'force': False})
       +        if ret and ret != 1:
       +                raise util.Abort(ret)
       +        out = fui.output
       +        return out
       +
       +def outgoing(repo):
       +        fui = FakeMercurialUI()
       +        ret = commands.outgoing(fui, repo, *[], **{})
       +        if ret and ret != 1:
       +                raise util.Abort(ret)
       +        out = fui.output
       +        return out
       +
        # To experiment with Mercurial in the python interpreter:
        #    >>> repo = hg.repository(ui.ui(), path = ".")
        
       t@@ -176,7 +192,9 @@ set_mercurial_encoding_to_utf8()
        # 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()
       t@@ -212,6 +230,7 @@ class CL(object):
                        self.copied_from = None        # None means current user
                        self.mailed = False
                        self.private = False
       +                self.lgtm = []
        
                def DiskText(self):
                        cl = self
       t@@ -264,6 +283,8 @@ class CL(object):
                        if cl.copied_from:
                                s += "\tAuthor: " + cl.copied_from + "\n"
                        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:
       t@@ -536,6 +557,13 @@ def LoadCL(ui, repo, name, web=True):
                        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:
       +                                who = re.sub('@.*', '', m.get('sender', ''))
       +                                text = re.sub("\n(.|\n)*", '', m.get('text', ''))
       +                                cl.lgtm.append((who, text))
       +
                set_status("loaded CL " + name)
                return cl, ''
        
       t@@ -713,14 +741,14 @@ _change_prolog = """# Change list.
        # Get effective change nodes taking into account applied MQ patches
        def effective_revpair(repo):
            try:
       -        return cmdutil.revpair(repo, ['qparent'])
       +        return scmutil.revpair(repo, ['qparent'])
            except:
       -        return cmdutil.revpair(repo, None)
       +        return scmutil.revpair(repo, None)
        
        # Return list of changed files in repository that match pats.
        # Warn about patterns that did not match.
        def matchpats(ui, repo, pats, opts):
       -        matcher = cmdutil.match(repo, pats, opts)
       +        matcher = scmutil.match(repo, pats, opts)
                node1, node2 = effective_revpair(repo)
                modified, added, removed, deleted, unknown, ignored, clean = repo.status(node1, node2, matcher, ignored=True, clean=True, unknown=True)
                return (modified, added, removed, deleted, unknown, ignored, clean)
       t@@ -802,10 +830,6 @@ def getremote(ui, repo, opts):
                        os.environ['http_proxy'] = proxy
                return other
        
       -def Incoming(ui, repo, opts):
       -        _, incoming, _ = findcommonincoming(repo, getremote(ui, repo, opts))
       -        return incoming
       -
        desc_re = '^(.+: |(tag )?(release|weekly)\.|fix build|undo CL)'
        
        desc_msg = '''Your CL description appears not to use the standard form.
       t@@ -827,7 +851,6 @@ Examples:
        
        '''
        
       -        
        
        def promptremove(ui, repo, f):
                if promptyesno(ui, "hg remove %s (y/n)?" % (f,)):
       t@@ -844,6 +867,18 @@ def EditCL(ui, repo, cl):
                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)):
       t@@ -941,25 +976,35 @@ def CommandLineCL(ui, repo, pats, opts, defaultcc=None):
        # which expands the syntax @clnumber to mean the files
        # in that CL.
        original_match = None
       -def ReplacementForCmdutilMatch(repo, pats=None, opts=None, globbed=False, default='relpath'):
       +global_repo = None
       +global_ui = None
       +def ReplacementForCmdutilMatch(ctx, pats=None, opts=None, globbed=False, default='relpath'):
                taken = []
                files = []
                pats = pats or []
                opts = opts or {}
       +        
                for p in pats:
                        if p.startswith('@'):
                                taken.append(p)
                                clname = p[1:]
       -                        if not GoodCLName(clname):
       -                                raise util.Abort("invalid CL name " + clname)
       -                        cl, err = LoadCL(repo.ui, repo, clname, web=False)
       -                        if err != '':
       -                                raise util.Abort("loading CL " + clname + ": " + err)
       -                        if not cl.files:
       -                                raise util.Abort("no files in CL " + clname)
       -                        files = Add(files, cl.files)
       +                        if clname == "default":
       +                                files = DefaultFiles(global_ui, global_repo, [], opts)
       +                        else:
       +                                if not GoodCLName(clname):
       +                                        raise util.Abort("invalid CL name " + clname)
       +                                cl, err = LoadCL(global_repo.ui, global_repo, clname, web=False)
       +                                if err != '':
       +                                        raise util.Abort("loading CL " + clname + ": " + err)
       +                                if not cl.files:
       +                                        raise util.Abort("no files in CL " + clname)
       +                                files = Add(files, cl.files)
                pats = Sub(pats, taken) + ['path:'+f for f in files]
       -        return original_match(repo, pats=pats, opts=opts, globbed=globbed, default=default)
       +
       +        # work-around for http://selenic.com/hg/rev/785bbc8634f8
       +        if hgversion >= '1.9' and not hasattr(ctx, 'match'):
       +                ctx = ctx[None]
       +        return original_match(ctx, pats=pats, opts=opts, globbed=globbed, default=default)
        
        def RelativePath(path, cwd):
                n = len(cwd)
       t@@ -1292,7 +1337,7 @@ def clpatch_or_undo(ui, repo, clname, opts, mode):
                        # sequence numbers get to be 7 digits long.
                        if re.match('^[0-9]{7,}$', clname):
                                found = False
       -                        matchfn = cmdutil.match(repo, [], {'rev': None})
       +                        matchfn = scmutil.match(repo, [], {'rev': None})
                                def prep(ctx, fns):
                                        pass
                                for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev': None}, prep):
       t@@ -1606,9 +1651,12 @@ def pending(ui, repo, *pats, **opts):
        def reposetup(ui, repo):
                global original_match
                if original_match is None:
       +                global global_repo, global_ui
       +                global_repo = repo
       +                global_ui = ui
                        start_status_thread()
       -                original_match = cmdutil.match
       -                cmdutil.match = ReplacementForCmdutilMatch
       +                original_match = scmutil.match
       +                scmutil.match = ReplacementForCmdutilMatch
                        RietveldSetup(ui, repo)
        
        def CheckContributor(ui, repo, user=None):
       t@@ -1648,8 +1696,9 @@ def submit(ui, repo, *pats, **opts):
                # We already called this on startup but sometimes Mercurial forgets.
                set_mercurial_encoding_to_utf8()
        
       +        other = getremote(ui, repo, opts)
                repo.ui.quiet = True
       -        if not opts["no_incoming"] and Incoming(ui, repo, opts):
       +        if not opts["no_incoming"] and incoming(repo, other):
                        return "local repository out of date; must sync before submit"
        
                cl, err = CommandLineCL(ui, repo, pats, opts, defaultcc=defaultcc)
       t@@ -1712,6 +1761,12 @@ def submit(ui, repo, *pats, **opts):
                        print Indent('\n'.join(cl.files), "\t")
                        return "dry run; not submitted"
        
       +        set_status("pushing " + cl.name + " to remote server")
       +
       +        other = getremote(ui, repo, opts)
       +        if outgoing(repo):
       +                raise util.Abort("local repository corrupt or out-of-phase with remote: found outgoing changes")
       +
                m = match.exact(repo.root, repo.getcwd(), cl.files)
                node = repo.commit(ustr(opts['message']), ustr(userline), opts.get('date'), m)
                if not node:
       t@@ -1732,7 +1787,6 @@ def submit(ui, repo, *pats, **opts):
                        # push changes to remote.
                        # if it works, we're committed.
                        # if not, roll back
       -                other = getremote(ui, repo, opts)
                        r = repo.push(other, False, None)
                        if r == 0:
                                raise util.Abort("local repository out of date; must sync before submit")
       t@@ -1828,7 +1882,7 @@ def sync_changes(ui, repo):
                                        break
                                Rev(rev)
                else:
       -                matchfn = cmdutil.match(repo, [], {'rev': None})
       +                matchfn = scmutil.match(repo, [], {'rev': None})
                        def prep(ctx, fns):
                                pass
                        for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev': None}, prep):
       t@@ -3087,6 +3141,7 @@ class VersionControlSystem(object):
                                return False
                        return not mimetype.startswith("text/")
        
       +
        class FakeMercurialUI(object):
                def __init__(self):
                        self.quiet = True
       t@@ -3094,6 +3149,19 @@ class FakeMercurialUI(object):
                
                def write(self, *args, **opts):
                        self.output += ' '.join(args)
       +        def copy(self):
       +                return self
       +        def status(self, *args, **opts):
       +                pass
       +        
       +        def readconfig(self, *args, **opts):
       +                pass
       +        def expandpath(self, *args, **opts):
       +                return global_ui.expandpath(*args, **opts)
       +        def configitems(self, *args, **opts):
       +                return global_ui.configitems(*args, **opts)
       +        def config(self, *args, **opts):
       +                return global_ui.config(*args, **opts)
        
        use_hg_shell = False        # set to True to shell out to hg always; slower
        
       t@@ -3104,6 +3172,7 @@ class MercurialVCS(VersionControlSystem):
                        super(MercurialVCS, self).__init__(options)
                        self.ui = ui
                        self.repo = repo
       +                self.status = None
                        # Absolute path to repository (we can be in a subdir)
                        self.repo_dir = os.path.normpath(repo.root)
                        # Compute the subdir
       t@@ -3162,6 +3231,33 @@ class MercurialVCS(VersionControlSystem):
                                        unknown_files.append(fn)
                        return unknown_files
        
       +        def get_hg_status(self, rev, path):
       +                # We'd like to use 'hg status -C path', but that is buggy
       +                # (see http://mercurial.selenic.com/bts/issue3023).
       +                # Instead, run 'hg status -C' without a path
       +                # and skim the output for the path we want.
       +                if self.status is None:
       +                        if use_hg_shell:
       +                                out = RunShell(["hg", "status", "-C", "--rev", rev])
       +                        else:
       +                                fui = FakeMercurialUI()
       +                                ret = commands.status(fui, self.repo, *[], **{'rev': [rev], 'copies': True})
       +                                if ret:
       +                                        raise util.Abort(ret)
       +                                out = fui.output
       +                        self.status = out.splitlines()
       +                for i in range(len(self.status)):
       +                        # line is
       +                        #        A path
       +                        #        M path
       +                        # etc
       +                        line = self.status[i].replace('\\', '/')
       +                        if line[2:] == path:
       +                                if i+1 < len(self.status) and self.status[i+1][:2] == '  ':
       +                                        return self.status[i:i+2]
       +                                return self.status[i:i+1]
       +                raise util.Abort("no status for " + path)
       +        
                def GetBaseFile(self, filename):
                        set_status("inspecting " + filename)
                        # "hg status" and "hg cat" both take a path relative to the current subdir
       t@@ -3171,20 +3267,7 @@ class MercurialVCS(VersionControlSystem):
                        new_content = None
                        is_binary = False
                        oldrelpath = relpath = self._GetRelPath(filename)
       -                # "hg status -C" returns two lines for moved/copied files, one otherwise
       -                if use_hg_shell:
       -                        out = RunShell(["hg", "status", "-C", "--rev", self.base_rev, relpath])
       -                else:
       -                        fui = FakeMercurialUI()
       -                        ret = commands.status(fui, self.repo, *[relpath], **{'rev': [self.base_rev], 'copies': True})
       -                        if ret:
       -                                raise util.Abort(ret)
       -                        out = fui.output
       -                out = out.splitlines()
       -                # HACK: strip error message about missing file/directory if it isn't in
       -                # the working copy
       -                if out[0].startswith('%s: ' % relpath):
       -                        out = out[1:]
       +                out = self.get_hg_status(self.base_rev, relpath)
                        status, what = out[0].split(' ', 1)
                        if len(out) > 1 and status == "A" and what == relpath:
                                oldrelpath = out[1].strip()