def duplicatecopies(repo, rev, p1, p2): "Reproduce copies found in the source revision in the dirstate for grafts" # Here we simulate the copies and renames in the source changeset cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True) m1 = repo[rev].manifest() m2 = repo[p1].manifest() for k, v in cop.iteritems(): if k in m1: if v in m1 or v in m2: repo.dirstate.copy(v, k) if v in m2 and v not in m1 and k in m2: repo.dirstate.remove(v)
def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None): '''yields diff of changes to files between two nodes, or node and working directory. if node1 is None, use first dirstate parent instead. if node2 is None, compare node1 with working directory.''' if opts is None: opts = mdiff.defaultopts if not node1: node1 = repo.dirstate.parents()[0] def lrugetfilectx(): cache = {} order = [] def getfilectx(f, ctx): fctx = ctx.filectx(f, filelog=cache.get(f)) if f not in cache: if len(cache) > 20: del cache[order.pop(0)] cache[f] = fctx._filelog else: order.remove(f) order.append(f) return fctx return getfilectx getfilectx = lrugetfilectx() ctx1 = repo[node1] ctx2 = repo[node2] if not changes: changes = repo.status(ctx1, ctx2, match=match) modified, added, removed = changes[:3] if not modified and not added and not removed: return date1 = util.datestr(ctx1.date()) man1 = ctx1.manifest() if repo.ui.quiet: r = None else: hexfunc = repo.ui.debugflag and hex or short r = [hexfunc(node) for node in [node1, node2] if node] if opts.git: copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid]) copy = copy.copy() for k, v in copy.items(): copy[v] = k gone = set() gitmode = {'l': '120000', 'x': '100755', '': '100644'} for f in sorted(modified + added + removed): to = None tn = None dodiff = True header = [] if f in man1: to = getfilectx(f, ctx1).data() if f not in removed: tn = getfilectx(f, ctx2).data() a, b = f, f if opts.git: if f in added: mode = gitmode[ctx2.flags(f)] if f in copy: a = copy[f] omode = gitmode[man1.flags(a)] _addmodehdr(header, omode, mode) if a in removed and a not in gone: op = 'rename' gone.add(a) else: op = 'copy' header.append('%s from %s\n' % (op, a)) header.append('%s to %s\n' % (op, f)) to = getfilectx(a, ctx1).data() else: header.append('new file mode %s\n' % mode) if util.binary(tn): dodiff = 'binary' elif f in removed: # have we already reported a copy above? if f in copy and copy[f] in added and copy[copy[f]] == f: dodiff = False else: header.append('deleted file mode %s\n' % gitmode[man1.flags(f)]) else: omode = gitmode[man1.flags(f)] nmode = gitmode[ctx2.flags(f)] _addmodehdr(header, omode, nmode) if util.binary(to) or util.binary(tn): dodiff = 'binary' r = None header.insert(0, mdiff.diffline(r, a, b, opts)) if dodiff: if dodiff == 'binary': text = b85diff(to, tn) else: text = mdiff.unidiff(to, date1, # ctx2 date may be dynamic tn, util.datestr(ctx2.date()), a, b, r, opts=opts) if header and (text or len(header) > 1): yield ''.join(header) if text: yield text
def manifestmerge(repo, p1, p2, pa, overwrite, partial): """ Merge p1 and p2 with ancestor ma and generate merge action list overwrite = whether we clobber working files partial = function to filter file lists """ repo.ui.note(_("resolving manifests\n")) repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial))) repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2)) m1 = p1.manifest() m2 = p2.manifest() ma = pa.manifest() backwards = (pa == p2) action = [] copy, copied, diverge = {}, {}, {} def fmerge(f, f2=None, fa=None): """merge flags""" if not f2: f2 = f fa = f a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2) if m == n: # flags agree return m # unchanged if m and n: # flags are set but don't agree if not a: # both differ from parent r = repo.ui.prompt( _(" conflicting flags for %s\n" "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n") return r != "n" and r or '' if m == a: return n # changed from m to n return m # changed from n to m if m and m != a: # changed from a to m return m if n and n != a: # changed from a to n return n return '' # flag was cleared def act(msg, m, f, *args): repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m)) action.append((f, m) + args) if pa and not (backwards or overwrite): if repo.ui.configbool("merge", "followcopies", True): dirs = repo.ui.configbool("merge", "followdirs", True) copy, diverge = copies.copies(repo, p1, p2, pa, dirs) copied = dict.fromkeys(copy.values()) for of, fl in diverge.items(): act("divergent renames", "dr", of, fl) # Compare manifests for f, n in m1.iteritems(): if partial and not partial(f): continue if f in m2: if overwrite or backwards: rflags = m2.flags(f) else: rflags = fmerge(f) # are files different? if n != m2[f]: a = ma.get(f, nullid) # are we clobbering? if overwrite: act("clobbering", "g", f, rflags) # or are we going back in time and clean? elif backwards and not n[20:]: act("reverting", "g", f, rflags) # are both different from the ancestor? elif n != a and m2[f] != a: act("versions differ", "m", f, f, f, rflags, False) # is remote's version newer? elif m2[f] != a: act("remote is newer", "g", f, rflags) # local is newer, not overwrite, check mode bits elif m1.flags(f) != rflags: act("update permissions", "e", f, rflags) # contents same, check mode bits elif m1.flags(f) != rflags: act("update permissions", "e", f, rflags) elif f in copied: continue elif f in copy: f2 = copy[f] if f2 not in m2: # directory rename act("remote renamed directory to " + f2, "d", f, None, f2, m1.flags(f)) elif f2 in m1: # case 2 A,B/B/B act("local copied to " + f2, "m", f, f2, f, fmerge(f, f2, f2), False) else: # case 4,21 A/B/B act("local moved to " + f2, "m", f, f2, f, fmerge(f, f2, f2), False) elif f in ma: if n != ma[f] and not overwrite: if repo.ui.prompt( _(" local changed %s which remote deleted\n" "use (c)hanged version or (d)elete?") % f, _("[cd]"), _("c")) == _("d"): act("prompt delete", "r", f) else: act("other deleted", "r", f) else: # file is created on branch or in working directory if (overwrite and n[20:] != "u") or (backwards and not n[20:]): act("remote deleted", "r", f) for f, n in m2.iteritems(): if partial and not partial(f): continue if f in m1: continue if f in copied: continue if f in copy: f2 = copy[f] if f2 not in m1: # directory rename act("local renamed directory to " + f2, "d", None, f, f2, m2.flags(f)) elif f2 in m2: # rename case 1, A/A,B/A act("remote copied to " + f, "m", f2, f, f, fmerge(f2, f, f2), False) else: # case 3,20 A/B/A act("remote moved to " + f, "m", f2, f, f, fmerge(f2, f, f2), True) elif f in ma: if overwrite or backwards: act("recreating", "g", f, m2.flags(f)) elif n != ma[f]: if repo.ui.prompt( _("remote changed %s which local deleted\n" "use (c)hanged version or leave (d)eleted?") % f, _("[cd]"), _("c")) == _("c"): act("prompt recreating", "g", f, m2.flags(f)) else: act("remote created", "g", f, m2.flags(f)) return action
def manifestmerge(repo, p1, p2, pa, overwrite, partial): """ Merge p1 and p2 with ancestor ma and generate merge action list overwrite = whether we clobber working files partial = function to filter file lists """ def fmerge(f, f2, fa): """merge flags""" a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2) if m == n: # flags agree return m # unchanged if m and n and not a: # flags set, don't agree, differ from parent r = repo.ui.promptchoice( _(" conflicting flags for %s\n" "(n)one, e(x)ec or sym(l)ink?") % f, (_("&None"), _("E&xec"), _("Sym&link")), 0) if r == 1: return "x" # Exec if r == 2: return "l" # Symlink return "" if m and m != a: # changed from a to m return m if n and n != a: # changed from a to n return n return '' # flag was cleared def act(msg, m, f, *args): repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m)) action.append((f, m) + args) action, copy = [], {} if overwrite: pa = p1 elif pa == p2: # backwards pa = p1.p1() elif pa and repo.ui.configbool("merge", "followcopies", True): dirs = repo.ui.configbool("merge", "followdirs", True) copy, diverge = copies.copies(repo, p1, p2, pa, dirs) for of, fl in diverge.iteritems(): act("divergent renames", "dr", of, fl) repo.ui.note(_("resolving manifests\n")) repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial))) repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2)) m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest() copied = set(copy.values()) # Compare manifests for f, n in m1.iteritems(): if partial and not partial(f): continue if f in m2: rflags = fmerge(f, f, f) a = ma.get(f, nullid) if n == m2[f] or m2[f] == a: # same or local newer if m1.flags(f) != rflags: act("update permissions", "e", f, rflags) elif n == a: # remote newer act("remote is newer", "g", f, rflags) else: # both changed act("versions differ", "m", f, f, f, rflags, False) elif f in copied: # files we'll deal with on m2 side pass elif f in copy: f2 = copy[f] if f2 not in m2: # directory rename act("remote renamed directory to " + f2, "d", f, None, f2, m1.flags(f)) else: # case 2 A,B/B/B or case 4,21 A/B/B act("local copied/moved to " + f2, "m", f, f2, f, fmerge(f, f2, f2), False) elif f in ma: # clean, a different, no remote if n != ma[f]: if repo.ui.promptchoice( _(" local changed %s which remote deleted\n" "use (c)hanged version or (d)elete?") % f, (_("&Changed"), _("&Delete")), 0): act("prompt delete", "r", f) else: act("prompt keep", "a", f) elif n[20:] == "a": # added, no remote act("remote deleted", "f", f) elif n[20:] != "u": act("other deleted", "r", f) for f, n in m2.iteritems(): if partial and not partial(f): continue if f in m1 or f in copied: # files already visited continue if f in copy: f2 = copy[f] if f2 not in m1: # directory rename act("local renamed directory to " + f2, "d", None, f, f2, m2.flags(f)) elif f2 in m2: # rename case 1, A/A,B/A act("remote copied to " + f, "m", f2, f, f, fmerge(f2, f, f2), False) else: # case 3,20 A/B/A act("remote moved to " + f, "m", f2, f, f, fmerge(f2, f, f2), True) elif f not in ma: act("remote created", "g", f, m2.flags(f)) elif n != ma[f]: if repo.ui.promptchoice( _("remote changed %s which local deleted\n" "use (c)hanged version or leave (d)eleted?") % f, (_("&Changed"), _("&Deleted")), 0) == 0: act("prompt recreating", "g", f, m2.flags(f)) return action
def diff(repo, node1=None, node2=None, files=None, match=util.always, fp=None, changes=None, opts=None): '''print diff of changes to files between two nodes, or node and working directory. if node1 is None, use first dirstate parent instead. if node2 is None, compare node1 with working directory.''' if opts is None: opts = mdiff.defaultopts if fp is None: fp = repo.ui if not node1: node1 = repo.dirstate.parents()[0] ccache = {} def getctx(r): if r not in ccache: ccache[r] = context.changectx(repo, r) return ccache[r] flcache = {} def getfilectx(f, ctx): flctx = ctx.filectx(f, filelog=flcache.get(f)) if f not in flcache: flcache[f] = flctx._filelog return flctx # reading the data for node1 early allows it to play nicely # with repo.status and the revlog cache. ctx1 = context.changectx(repo, node1) # force manifest reading man1 = ctx1.manifest() date1 = util.datestr(ctx1.date()) if not changes: changes = repo.status(node1, node2, files, match=match)[:5] modified, added, removed, deleted, unknown = changes if not modified and not added and not removed: return if node2: ctx2 = context.changectx(repo, node2) execf2 = ctx2.manifest().execf linkf2 = ctx2.manifest().linkf else: ctx2 = context.workingctx(repo) execf2 = util.execfunc(repo.root, None) linkf2 = util.linkfunc(repo.root, None) if execf2 is None: mc = ctx2.parents()[0].manifest().copy() execf2 = mc.execf linkf2 = mc.linkf if repo.ui.quiet: r = None else: hexfunc = repo.ui.debugflag and hex or short r = [hexfunc(node) for node in [node1, node2] if node] if opts.git: copy, diverge = copies.copies(repo, ctx1, ctx2, repo.changectx(nullid)) for k, v in copy.items(): copy[v] = k all = modified + added + removed all.sort() gone = {} for f in all: to = None tn = None dodiff = True header = [] if f in man1: to = getfilectx(f, ctx1).data() if f not in removed: tn = getfilectx(f, ctx2).data() a, b = f, f if opts.git: def gitmode(x, l): return l and '120000' or (x and '100755' or '100644') def addmodehdr(header, omode, nmode): if omode != nmode: header.append('old mode %s\n' % omode) header.append('new mode %s\n' % nmode) if f in added: mode = gitmode(execf2(f), linkf2(f)) if f in copy: a = copy[f] omode = gitmode(man1.execf(a), man1.linkf(a)) addmodehdr(header, omode, mode) if a in removed and a not in gone: op = 'rename' gone[a] = 1 else: op = 'copy' header.append('%s from %s\n' % (op, a)) header.append('%s to %s\n' % (op, f)) to = getfilectx(a, ctx1).data() else: header.append('new file mode %s\n' % mode) if util.binary(tn): dodiff = 'binary' elif f in removed: # have we already reported a copy above? if f in copy and copy[f] in added and copy[copy[f]] == f: dodiff = False else: mode = gitmode(man1.execf(f), man1.linkf(f)) header.append('deleted file mode %s\n' % mode) else: omode = gitmode(man1.execf(f), man1.linkf(f)) nmode = gitmode(execf2(f), linkf2(f)) addmodehdr(header, omode, nmode) if util.binary(to) or util.binary(tn): dodiff = 'binary' r = None header.insert(0, 'diff --git a/%s b/%s\n' % (a, b)) if dodiff: if dodiff == 'binary': text = b85diff(to, tn) else: text = mdiff.unidiff(to, date1, # ctx2 date may be dynamic tn, util.datestr(ctx2.date()), a, b, r, opts=opts) if text or len(header) > 1: fp.write(''.join(header)) fp.write(text)
def manifestmerge(repo, p1, p2, pa, overwrite, partial): """ Merge p1 and p2 with ancestor pa and generate merge action list overwrite = whether we clobber working files partial = function to filter file lists """ def fmerge(f, f2, fa): """merge flags""" a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2) if m == n: # flags agree return m # unchanged if m and n and not a: # flags set, don't agree, differ from parent r = repo.ui.promptchoice( _(" conflicting flags for %s\n" "(n)one, e(x)ec or sym(l)ink?") % f, (_("&None"), _("E&xec"), _("Sym&link")), 0) if r == 1: return "x" # Exec if r == 2: return "l" # Symlink return "" if m and m != a: # changed from a to m return m if n and n != a: # changed from a to n return n return '' # flag was cleared def act(msg, m, f, *args): repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m)) action.append((f, m) + args) action, copy = [], {} if overwrite: pa = p1 elif pa == p2: # backwards pa = p1.p1() elif pa and repo.ui.configbool("merge", "followcopies", True): dirs = repo.ui.configbool("merge", "followdirs", True) copy, diverge = copies.copies(repo, p1, p2, pa, dirs) for of, fl in diverge.iteritems(): act("divergent renames", "dr", of, fl) repo.ui.note(_("resolving manifests\n")) repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial))) repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2)) m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest() copied = set(copy.values()) if '.hgsubstate' in m1: # check whether sub state is modified for s in p1.substate: if p1.sub(s).dirty(): m1['.hgsubstate'] += "+" break # Compare manifests for f, n in m1.iteritems(): if partial and not partial(f): continue if f in m2: rflags = fmerge(f, f, f) a = ma.get(f, nullid) if n == m2[f] or m2[f] == a: # same or local newer # is file locally modified or flags need changing? # dirstate flags may need to be made current if m1.flags(f) != rflags or n[20:]: act("update permissions", "e", f, rflags) elif n == a: # remote newer act("remote is newer", "g", f, rflags) else: # both changed act("versions differ", "m", f, f, f, rflags, False) elif f in copied: # files we'll deal with on m2 side pass elif f in copy: f2 = copy[f] if f2 not in m2: # directory rename act("remote renamed directory to " + f2, "d", f, None, f2, m1.flags(f)) else: # case 2 A,B/B/B or case 4,21 A/B/B act("local copied/moved to " + f2, "m", f, f2, f, fmerge(f, f2, f2), False) elif f in ma: # clean, a different, no remote if n != ma[f]: if repo.ui.promptchoice( _(" local changed %s which remote deleted\n" "use (c)hanged version or (d)elete?") % f, (_("&Changed"), _("&Delete")), 0): act("prompt delete", "r", f) else: act("prompt keep", "a", f) elif n[20:] == "a": # added, no remote act("remote deleted", "f", f) elif n[20:] != "u": act("other deleted", "r", f) for f, n in m2.iteritems(): if partial and not partial(f): continue if f in m1 or f in copied: # files already visited continue if f in copy: f2 = copy[f] if f2 not in m1: # directory rename act("local renamed directory to " + f2, "d", None, f, f2, m2.flags(f)) elif f2 in m2: # rename case 1, A/A,B/A act("remote copied to " + f, "m", f2, f, f, fmerge(f2, f, f2), False) else: # case 3,20 A/B/A act("remote moved to " + f, "m", f2, f, f, fmerge(f2, f, f2), True) elif f not in ma: act("remote created", "g", f, m2.flags(f)) elif n != ma[f]: if repo.ui.promptchoice( _("remote changed %s which local deleted\n" "use (c)hanged version or leave (d)eleted?") % f, (_("&Changed"), _("&Deleted")), 0) == 0: act("prompt recreating", "g", f, m2.flags(f)) return action
def diff(repo, node1=None, node2=None, files=None, match=util.always, fp=None, changes=None, opts=None): '''print diff of changes to files between two nodes, or node and working directory. if node1 is None, use first dirstate parent instead. if node2 is None, compare node1 with working directory.''' if opts is None: opts = mdiff.defaultopts if fp is None: fp = repo.ui if not node1: node1 = repo.dirstate.parents()[0] ccache = {} def getctx(r): if r not in ccache: ccache[r] = context.changectx(repo, r) return ccache[r] flcache = {} def getfilectx(f, ctx): flctx = ctx.filectx(f, filelog=flcache.get(f)) if f not in flcache: flcache[f] = flctx._filelog return flctx # reading the data for node1 early allows it to play nicely # with repo.status and the revlog cache. ctx1 = context.changectx(repo, node1) # force manifest reading man1 = ctx1.manifest() date1 = util.datestr(ctx1.date()) if not changes: changes = repo.status(node1, node2, files, match=match)[:5] modified, added, removed, deleted, unknown = changes if not modified and not added and not removed: return if node2: ctx2 = context.changectx(repo, node2) execf2 = ctx2.manifest().execf linkf2 = ctx2.manifest().linkf else: ctx2 = context.workingctx(repo) execf2 = util.execfunc(repo.root, None) linkf2 = util.linkfunc(repo.root, None) if execf2 is None: mc = ctx2.parents()[0].manifest().copy() execf2 = mc.execf linkf2 = mc.linkf if repo.ui.quiet: r = None else: hexfunc = repo.ui.debugflag and hex or short r = [hexfunc(node) for node in [node1, node2] if node] if opts.git: copy, diverge = copies.copies(repo, ctx1, ctx2, repo.changectx(nullid)) for k, v in copy.items(): copy[v] = k all = modified + added + removed all.sort() gone = {} for f in all: to = None tn = None dodiff = True header = [] if f in man1: to = getfilectx(f, ctx1).data() if f not in removed: tn = getfilectx(f, ctx2).data() a, b = f, f if opts.git: def gitmode(x, l): return l and '120000' or (x and '100755' or '100644') def addmodehdr(header, omode, nmode): if omode != nmode: header.append('old mode %s\n' % omode) header.append('new mode %s\n' % nmode) if f in added: mode = gitmode(execf2(f), linkf2(f)) if f in copy: a = copy[f] omode = gitmode(man1.execf(a), man1.linkf(a)) addmodehdr(header, omode, mode) if a in removed and a not in gone: op = 'rename' gone[a] = 1 else: op = 'copy' header.append('%s from %s\n' % (op, a)) header.append('%s to %s\n' % (op, f)) to = getfilectx(a, ctx1).data() else: header.append('new file mode %s\n' % mode) if util.binary(tn): dodiff = 'binary' elif f in removed: # have we already reported a copy above? if f in copy and copy[f] in added and copy[copy[f]] == f: dodiff = False else: mode = gitmode(man1.execf(f), man1.linkf(f)) header.append('deleted file mode %s\n' % mode) else: omode = gitmode(man1.execf(f), man1.linkf(f)) nmode = gitmode(execf2(f), linkf2(f)) addmodehdr(header, omode, nmode) if util.binary(to) or util.binary(tn): dodiff = 'binary' r = None header.insert(0, 'diff --git a/%s b/%s\n' % (a, b)) if dodiff: if dodiff == 'binary': text = b85diff(to, tn) else: text = mdiff.unidiff( to, date1, # ctx2 date may be dynamic tn, util.datestr(ctx2.date()), a, b, r, opts=opts) if text or len(header) > 1: fp.write(''.join(header)) fp.write(text)
def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None): """yields diff of changes to files between two nodes, or node and working directory. if node1 is None, use first dirstate parent instead. if node2 is None, compare node1 with working directory.""" if opts is None: opts = mdiff.defaultopts if not node1: node1 = repo.dirstate.parents()[0] flcache = {} def getfilectx(f, ctx): flctx = ctx.filectx(f, filelog=flcache.get(f)) if f not in flcache: flcache[f] = flctx._filelog return flctx ctx1 = repo[node1] ctx2 = repo[node2] if not changes: changes = repo.status(ctx1, ctx2, match=match) modified, added, removed = changes[:3] if not modified and not added and not removed: return date1 = util.datestr(ctx1.date()) man1 = ctx1.manifest() if repo.ui.quiet: r = None else: hexfunc = repo.ui.debugflag and hex or short r = [hexfunc(node) for node in [node1, node2] if node] if opts.git: copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid]) for k, v in copy.items(): copy[v] = k gone = {} gitmode = {"l": "120000", "x": "100755", "": "100644"} for f in util.sort(modified + added + removed): to = None tn = None dodiff = True header = [] if f in man1: to = getfilectx(f, ctx1).data() if f not in removed: tn = getfilectx(f, ctx2).data() a, b = f, f if opts.git: if f in added: mode = gitmode[ctx2.flags(f)] if f in copy: a = copy[f] omode = gitmode[man1.flags(a)] _addmodehdr(header, omode, mode) if a in removed and a not in gone: op = "rename" gone[a] = 1 else: op = "copy" header.append("%s from %s\n" % (op, a)) header.append("%s to %s\n" % (op, f)) to = getfilectx(a, ctx1).data() else: header.append("new file mode %s\n" % mode) if util.binary(tn): dodiff = "binary" elif f in removed: # have we already reported a copy above? if f in copy and copy[f] in added and copy[copy[f]] == f: dodiff = False else: header.append("deleted file mode %s\n" % gitmode[man1.flags(f)]) else: omode = gitmode[man1.flags(f)] nmode = gitmode[ctx2.flags(f)] _addmodehdr(header, omode, nmode) if util.binary(to) or util.binary(tn): dodiff = "binary" r = None header.insert(0, mdiff.diffline(r, a, b, opts)) if dodiff: if dodiff == "binary": text = b85diff(to, tn) else: text = mdiff.unidiff( to, date1, # ctx2 date may be dynamic tn, util.datestr(ctx2.date()), a, b, r, opts=opts, ) if header and (text or len(header) > 1): yield "".join(header) if text: yield text