예제 #1
0
def showsyncstatus(repo, ctx, templ, **args):
    """String. Return whether the local revision is in sync
        with the remote (phabricator) revision
    """
    diffnum = getdiffnum(repo, ctx)
    if diffnum is None:
        return None

    populateresponseforphab(repo, diffnum)
    results = getdiffstatus(repo, diffnum)
    try:
        result = results[0]
        remote = result["hash"]
        status = result["status"]
        count = int(result["count"])
    except (IndexError, KeyError, ValueError, TypeError):
        # We got no result back, or it did not contain all required fields
        return "Error"

    local = ctx.hex()
    if local == remote:
        return "sync"
    elif count == 1:
        precursors = list(obsutil.allpredecessors(repo.obsstore, [ctx.node()]))
        hashes = [repo.unfiltered()[h].hex() for h in precursors]
        # hashes[0] is the current
        # hashes[1] is the previous
        if len(hashes) > 1 and hashes[1] == remote:
            return "sync"
        else:
            return "unsync"
    elif status == "Committed":
        return "committed"
    else:
        return "unsync"
예제 #2
0
def smarthide(repo, revhide, revshow, local=False):
    '''hides changecontexts and reveals some commits

    tries to connect related hides and shows with obs marker
    when reasonable and correct

    use local to not hide revhides without corresponding revshows
    '''
    hidectxs = repo.set(revhide)
    showctxs = repo.set(revshow)
    for ctx in hidectxs:
        unfi = repo.unfiltered()
        related = []
        related = set(obsutil.allpredecessors(unfi.obsstore, [ctx.node()]))
        related.update(obsutil.allsuccessors(unfi.obsstore, [ctx.node()]))
        related.intersection_update(x.node() for x in showctxs)
        destinations = [repo[x] for x in related]

        # two primary objectives:
        # 1. correct divergence/nondivergence
        # 2. correct visibility of changesets for the user
        # secondary objectives:
        # 3. usefull ui message in hg sl: "Undone to"
        # Design choices:
        # 1-to-1 correspondence is easy
        # 1-to-many correspondence is hard:
        #   it's either divergent A to B, A to C
        #   or split A to B,C
        #   because of undo we don't know which
        #   without complex logic
        # Solution: provide helpfull ui message for
        # common and easy case (1 to 1), use simplest
        # correct solution for complex edge case

        if len(destinations) == 1:
            hidecommits(repo, ctx, destinations)
        elif len(destinations) > 1: # split
            hidecommits(repo, ctx, [])
        elif len(destinations) == 0:
            if not local:
                hidecommits(repo, ctx, [])
예제 #3
0
def getoldnodedrevmap(repo, nodelist):
    """find previous nodes that has been sent to Phabricator

    return {node: (oldnode, Differential diff, Differential Revision ID)}
    for node in nodelist with known previous sent versions, or associated
    Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
    be ``None``.

    Examines commit messages like "Differential Revision:" to get the
    association information.

    If such commit message line is not found, examines all precursors and their
    tags. Tags with format like "D1234" are considered a match and the node
    with that tag, and the number after "D" (ex. 1234) will be returned.

    The ``old node``, if not None, is guaranteed to be the last diff of
    corresponding Differential Revision, and exist in the repo.
    """
    url, token = readurltoken(repo)
    unfi = repo.unfiltered()
    nodemap = unfi.changelog.nodemap

    result = {} # {node: (oldnode?, lastdiff?, drev)}
    toconfirm = {} # {node: (force, {precnode}, drev)}
    for node in nodelist:
        ctx = unfi[node]
        # For tags like "D123", put them into "toconfirm" to verify later
        precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
        for n in precnodes:
            if n in nodemap:
                for tag in unfi.nodetags(n):
                    m = _differentialrevisiontagre.match(tag)
                    if m:
                        toconfirm[node] = (0, set(precnodes), int(m.group(1)))
                        continue

        # Check commit message
        m = _differentialrevisiondescre.search(ctx.description())
        if m:
            toconfirm[node] = (1, set(precnodes), int(m.group(b'id')))

    # Double check if tags are genuine by collecting all old nodes from
    # Phabricator, and expect precursors overlap with it.
    if toconfirm:
        drevs = [drev for force, precs, drev in toconfirm.values()]
        alldiffs = callconduit(unfi, b'differential.querydiffs',
                               {b'revisionIDs': drevs})
        getnode = lambda d: bin(encoding.unitolocal(
            getdiffmeta(d).get(r'node', b''))) or None
        for newnode, (force, precset, drev) in toconfirm.items():
            diffs = [d for d in alldiffs.values()
                     if int(d[r'revisionID']) == drev]

            # "precursors" as known by Phabricator
            phprecset = set(getnode(d) for d in diffs)

            # Ignore if precursors (Phabricator and local repo) do not overlap,
            # and force is not set (when commit message says nothing)
            if not force and not bool(phprecset & precset):
                tagname = b'D%d' % drev
                tags.tag(repo, tagname, nullid, message=None, user=None,
                         date=None, local=True)
                unfi.ui.warn(_(b'D%s: local tag removed - does not match '
                               b'Differential history\n') % drev)
                continue

            # Find the last node using Phabricator metadata, and make sure it
            # exists in the repo
            oldnode = lastdiff = None
            if diffs:
                lastdiff = max(diffs, key=lambda d: int(d[r'id']))
                oldnode = getnode(lastdiff)
                if oldnode and oldnode not in nodemap:
                    oldnode = None

            result[newnode] = (oldnode, lastdiff, drev)

    return result
예제 #4
0
def getoldnodedrevmap(repo, nodelist):
    """find previous nodes that has been sent to Phabricator

    return {node: (oldnode, Differential diff, Differential Revision ID)}
    for node in nodelist with known previous sent versions, or associated
    Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
    be ``None``.

    Examines commit messages like "Differential Revision:" to get the
    association information.

    If such commit message line is not found, examines all precursors and their
    tags. Tags with format like "D1234" are considered a match and the node
    with that tag, and the number after "D" (ex. 1234) will be returned.

    The ``old node``, if not None, is guaranteed to be the last diff of
    corresponding Differential Revision, and exist in the repo.
    """
    url, token = readurltoken(repo)
    unfi = repo.unfiltered()
    nodemap = unfi.changelog.nodemap

    result = {} # {node: (oldnode?, lastdiff?, drev)}
    toconfirm = {} # {node: (force, {precnode}, drev)}
    for node in nodelist:
        ctx = unfi[node]
        # For tags like "D123", put them into "toconfirm" to verify later
        precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
        for n in precnodes:
            if n in nodemap:
                for tag in unfi.nodetags(n):
                    m = _differentialrevisiontagre.match(tag)
                    if m:
                        toconfirm[node] = (0, set(precnodes), int(m.group(1)))
                        continue

        # Check commit message
        m = _differentialrevisiondescre.search(ctx.description())
        if m:
            toconfirm[node] = (1, set(precnodes), int(m.group(b'id')))

    # Double check if tags are genuine by collecting all old nodes from
    # Phabricator, and expect precursors overlap with it.
    if toconfirm:
        drevs = [drev for force, precs, drev in toconfirm.values()]
        alldiffs = callconduit(unfi, b'differential.querydiffs',
                               {b'revisionIDs': drevs})
        getnode = lambda d: bin(encoding.unitolocal(
            getdiffmeta(d).get(r'node', b''))) or None
        for newnode, (force, precset, drev) in toconfirm.items():
            diffs = [d for d in alldiffs.values()
                     if int(d[r'revisionID']) == drev]

            # "precursors" as known by Phabricator
            phprecset = set(getnode(d) for d in diffs)

            # Ignore if precursors (Phabricator and local repo) do not overlap,
            # and force is not set (when commit message says nothing)
            if not force and not bool(phprecset & precset):
                tagname = b'D%d' % drev
                tags.tag(repo, tagname, nullid, message=None, user=None,
                         date=None, local=True)
                unfi.ui.warn(_(b'D%s: local tag removed - does not match '
                               b'Differential history\n') % drev)
                continue

            # Find the last node using Phabricator metadata, and make sure it
            # exists in the repo
            oldnode = lastdiff = None
            if diffs:
                lastdiff = max(diffs, key=lambda d: int(d[r'id']))
                oldnode = getnode(lastdiff)
                if oldnode and oldnode not in nodemap:
                    oldnode = None

            result[newnode] = (oldnode, lastdiff, drev)

    return result
예제 #5
0
def allpredecessors(repo, subset, x):
    """All changesets which are predecessors for given set, recursively"""
    f = lambda nodes: obsutil.allpredecessors(repo.obsstore, nodes)
    return _calculateset(repo, subset, x, f)
예제 #6
0
    def send(self, ctx, count, data):
        '''send message.'''

        # Select subscribers by revset
        subs = set()
        for sub, spec in self.subs:
            if spec is None:
                subs.add(sub)
                continue
            revs = self.repo.revs(b'%r and %d:', spec, ctx.rev())
            if len(revs):
                subs.add(sub)
                continue
        if len(subs) == 0:
            self.ui.debug(
                b'notify: no subscribers to selected repo and revset\n')
            return

        try:
            msg = mail.parsebytes(data)
        except emailerrors.MessageParseError as inst:
            raise error.Abort(inst)

        # store sender and subject
        sender = msg['From']
        subject = msg['Subject']
        if sender is not None:
            sender = mail.headdecode(sender)
        if subject is not None:
            subject = mail.headdecode(subject)
        del msg['From'], msg['Subject']

        if not msg.is_multipart():
            # create fresh mime message from scratch
            # (multipart templates must take care of this themselves)
            headers = msg.items()
            payload = msg.get_payload(decode=pycompat.ispy3)
            # for notification prefer readability over data precision
            msg = mail.mimeencode(self.ui, payload, self.charsets, self.test)
            # reinstate custom headers
            for k, v in headers:
                msg[k] = v

        msg['Date'] = encoding.strfromlocal(
            dateutil.datestr(format=b"%a, %d %b %Y %H:%M:%S %1%2"))

        # try to make subject line exist and be useful
        if not subject:
            if count > 1:
                subject = _(b'%s: %d new changesets') % (self.root, count)
            else:
                s = ctx.description().lstrip().split(b'\n', 1)[0].rstrip()
                subject = b'%s: %s' % (self.root, s)
        maxsubject = int(self.ui.config(b'notify', b'maxsubject'))
        if maxsubject:
            subject = stringutil.ellipsis(subject, maxsubject)
        msg['Subject'] = mail.headencode(self.ui, subject, self.charsets,
                                         self.test)

        # try to make message have proper sender
        if not sender:
            sender = self.ui.config(b'email', b'from') or self.ui.username()
        if b'@' not in sender or b'@localhost' in sender:
            sender = self.fixmail(sender)
        msg['From'] = mail.addressencode(self.ui, sender, self.charsets,
                                         self.test)

        msg['X-Hg-Notification'] = 'changeset %s' % ctx
        if not msg['Message-Id']:
            msg['Message-Id'] = messageid(ctx, self.domain, self.messageidseed)
        if self.reply:
            unfi = self.repo.unfiltered()
            has_node = unfi.changelog.index.has_node
            predecessors = [
                unfi[ctx2] for ctx2 in obsutil.allpredecessors(
                    unfi.obsstore, [ctx.node()])
                if ctx2 != ctx.node() and has_node(ctx2)
            ]
            if predecessors:
                # There is at least one predecessor, so which to pick?
                # Ideally, there is a unique root because changesets have
                # been evolved/rebased one step at a time. In this case,
                # just picking the oldest known changeset provides a stable
                # base. It doesn't help when changesets are folded. Any
                # better solution would require storing more information
                # in the repository.
                pred = min(predecessors, key=lambda ctx: ctx.rev())
                msg['In-Reply-To'] = messageid(pred, self.domain,
                                               self.messageidseed)
        msg['To'] = ', '.join(sorted(subs))

        msgtext = msg.as_bytes() if pycompat.ispy3 else msg.as_string()
        if self.test:
            self.ui.write(msgtext)
            if not msgtext.endswith(b'\n'):
                self.ui.write(b'\n')
        else:
            self.ui.status(
                _(b'notify: sending %d subscribers %d changes\n') %
                (len(subs), count))
            mail.sendmail(
                self.ui,
                emailutils.parseaddr(msg['From'])[1],
                subs,
                msgtext,
                mbox=self.mbox,
            )