def _amend(orig, ui, repo, old, extra, pats, opts): """Wraps amend to collect copytrace data on amend If a file is created in one commit, modified in a subsequent commit, and then renamed or copied by amending the original commit, restacking the commits that modify the file will fail: file modified here B B' restack of B to B' will fail | : file created here A --> A' file renamed in amended commit | / o -- This function collects information about copies and renames from amend commits, and saves it for use during rebases onto the amend commit. This lets rebases onto files that been renamed or copied in an amend commit work without conflicts. This function collects the copytrace information from the working copy and stores it against the amended commit in a separate dbm file. Later, in _domergecopies, this information will be merged with the rebase copytrace data to incorporate renames and copies made during the amend. """ # Check if amend copytracing has been disabled. if not ui.configbool("copytrace", "enableamendcopytrace"): return orig(ui, repo, old, extra, pats, opts) # Need to get the amend-copies before calling the command because files from # the working copy will be used during the amend. wctx = repo[None] # Find the amend-copies. matcher = scmutil.match(wctx, pats, opts) amend_copies = copiesmod.pathcopies(old, wctx, matcher) # Finally, invoke the command. node = orig(ui, repo, old, extra, pats, opts) amended_ctx = repo[node] # Store the amend-copies against the amended context. if amend_copies: db, error = opendbm(repo, "c") if db is None: # Database locked, can't record these amend-copies. ui.log("copytrace", "Failed to open amendcopytrace db: %s" % error) return node # Merge in any existing amend copies from any previous amends. try: orig_data = db[old.node()] except KeyError: orig_data = "{}" except error as e: ui.log( "copytrace", "Failed to read key %s from amendcopytrace db: %s" % (old.hex(), e), ) return node orig_encoded = json.loads(orig_data) orig_amend_copies = dict( ( pycompat.decodeutf8( codecs.decode(pycompat.encodeutf8(k), "base64")), pycompat.decodeutf8( codecs.decode(pycompat.encodeutf8(v), "base64")), ) for (k, v) in pycompat.iteritems(orig_encoded)) # Copytrace information is not valid if it refers to a file that # doesn't exist in a commit. We need to update or remove entries # that refer to files that might have only existed in the previous # amend commit. # # Find chained copies and renames (a -> b -> c) and collapse them to # (a -> c). Delete the entry for b if this was a rename. for dst, src in pycompat.iteritems(amend_copies): if src in orig_amend_copies: amend_copies[dst] = orig_amend_copies[src] if src not in amended_ctx: del orig_amend_copies[src] # Copy any left over copies from the previous context. for dst, src in pycompat.iteritems(orig_amend_copies): if dst not in amend_copies: amend_copies[dst] = src # Write out the entry for the new amend commit. encoded = dict( ( pycompat.decodeutf8( codecs.encode(pycompat.encodeutf8(k), "base64")), pycompat.decodeutf8( codecs.encode(pycompat.encodeutf8(v), "base64")), ) for (k, v) in pycompat.iteritems(amend_copies)) db[node] = json.dumps(encoded) try: db.close() except Exception as e: # Database corruption. Not much we can do, so just log. ui.log("copytrace", "Failed to close amendcopytrace db: %s" % e) return node
def rewrite(repo, old, updates, head, newbases, commitopts, mutop=None): """Return (nodeid, created) where nodeid is the identifier of the changeset generated by the rewrite process, and created is True if nodeid was actually created. If created is False, nodeid references a changeset existing before the rewrite call. """ wlock = lock = tr = None try: wlock = repo.wlock() lock = repo.lock() tr = repo.transaction("rewrite") if len(old.parents()) > 1: # XXX remove this unnecessary limitation. raise error.Abort(_("cannot amend merge changesets")) base = old.p1() updatebookmarks = bookmarksupdater(repo, [old.node()] + [u.node() for u in updates]) # commit a new version of the old changeset, including the update # collect all files which might be affected files = set(old.files()) for u in updates: files.update(u.files()) # Recompute copies (avoid recording a -> b -> a) copied = copies.pathcopies(base, head) # prune files which were reverted by the updates def samefile(f): if f in head.manifest(): a = head.filectx(f) if f in base.manifest(): b = base.filectx(f) return a.data() == b.data() and a.flags() == b.flags() else: return False else: return f not in base.manifest() files = [f for f in files if not samefile(f)] # commit version of these files as defined by head headmf = head.manifest() def filectxfn(repo, ctx, path): if path in headmf: fctx = head[path] flags = fctx.flags() mctx = context.memfilectx( repo, ctx, fctx.path(), fctx.data(), islink="l" in flags, isexec="x" in flags, copied=copied.get(path), ) return mctx return None message = cmdutil.logmessage(repo, commitopts) if not message: message = old.description() user = commitopts.get("user") or old.user() # TODO: In case not date is given, we should take the old commit date # if we are working one one changeset or mimic the fold behavior about # date date = commitopts.get("date") or None extra = dict(commitopts.get("extra", old.extra())) extra["branch"] = head.branch() mutinfo = mutation.record(repo, extra, [c.node() for c in updates], mutop) loginfo = { "predecessors": " ".join(c.hex() for c in updates), "mutation": mutop, } new = context.memctx( repo, parents=newbases, text=message, files=files, filectxfn=filectxfn, user=user, date=date, extra=extra, loginfo=loginfo, mutinfo=mutinfo, ) if commitopts.get("edit"): new._text = cmdutil.commitforceeditor(repo, new, []) revcount = len(repo) newid = repo.commitctx(new) new = repo[newid] created = len(repo) != revcount updatebookmarks(newid) tr.close() return newid, created finally: lockmod.release(tr, lock, wlock)
def d(): copies.pathcopies(ctx1, ctx2)
def debugfillinfinitepushmetadata(ui, repo, **opts): """Special command that fills infinitepush metadata for a node """ nodes = opts["node"] if not nodes: raise error.Abort(_("nodes are not specified")) filelimit = ui.configint("infinitepush", "metadatafilelimit", 100) nodesmetadata = {} for node in nodes: index = repo.bundlestore.index if not bool(index.getbundle(node)): raise error.Abort(_("node %s is not found") % node) if node not in repo: newbundlefile = server.downloadbundle(repo, bin(node)) bundlepath = "bundle:%s+%s" % (repo.root, newbundlefile) bundlerepo = hg.repository(ui, bundlepath) repo = bundlerepo p1 = repo[node].p1().node() diffopts = patch.diffallopts(ui, {}) match = scmutil.matchall(repo) chunks = patch.diff(repo, p1, node, match, None, diffopts, relroot="") difflines = util.iterlines(chunks) states = "modified added removed deleted unknown ignored clean".split() status = repo.status(p1, node) status = zip(states, status) filestatus = {} for state, files in status: for f in files: filestatus[f] = state diffstat = patch.diffstatdata(difflines) changed_files = {} copies = copiesmod.pathcopies(repo[p1], repo[node]) for filename, adds, removes, isbinary in diffstat[:filelimit]: # use special encoding that allows non-utf8 filenames filename = pycompat.decodeutf8( encoding.jsonescape(pycompat.encodeutf8(filename), paranoid=True)) changed_files[filename] = { "adds": adds, "removes": removes, "isbinary": isbinary, "status": filestatus.get(filename, "unknown"), } if filename in copies: changed_files[filename]["copies"] = copies[filename] output = {} output["changed_files"] = changed_files if len(diffstat) > filelimit: output["changed_files_truncated"] = True nodesmetadata[node] = output with index: for node, metadata in pycompat.iteritems(nodesmetadata): dumped = json.dumps(metadata, sort_keys=True) index.saveoptionaljsonmetadata(node, pycompat.encodeutf8(dumped))