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"
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, [])
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
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)
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, )