def _iscreatedbyfb(path): """Returns True if path was created by FB. This function is very slow. So it uses ~/.cache/testutil/authordb/ as cache. """ cachepath = os.path.expanduser("~/.cache/testutil/authordb/%s" % hashlib.sha1(path).hexdigest()) if not os.path.exists(cachepath): util.makedirs(os.path.dirname(cachepath)) lines = sorted( subprocess.check_output( ["hg", "log", "-f", "-T{author|email}\n", path]).splitlines()) result = all(l.endswith("@fb.com") for l in lines) open(cachepath, "w").write(repr(result)) return ast.literal_eval(util.readfile(cachepath))
def symlink_failure(src, dst): raise OSError(1, "Operation not permitted") os.symlink = symlink_failure fscap.getfscap = lambda *args: None def islink_failure(path): return False os.path.islink = islink_failure # dereference links as if a Samba server has exported this to a # Windows client for f in "test0/a.lnk", "test0/d/b.lnk": os.unlink(f) fp = open(f, "wb") fp.write(util.readfile(f[:-4])) fp.close() # reload repository u = uimod.ui.load() repo = hg.repository(u, "test0") commands.status(u, repo) # try cloning a repo which contains symlinks u = uimod.ui.load() hg.clone(u, {}, BUNDLEPATH, "test1")
def metaedit(ui, repo, templ, *revs, **opts): """edit commit message and other metadata Edit commit message for the current commit. By default, opens your default editor so that you can edit the commit message interactively. Specify -m to specify the commit message on the command line. To edit the message for a different commit, specify -r. To edit the messages of multiple commits, specify --batch. You can edit other pieces of commit metadata, namely the user or date, by specifying -u or -d, respectively. The expected format for user is 'Full Name <*****@*****.**>'. There is also automation-friendly JSON input mode which allows the caller to provide the mapping between commit and new message and username in the following format: { "<commit_hash>": { "message": "<message>", "user": "******" // optional } } .. note:: You can specify --fold to fold multiple revisions into one when the given revisions form a linear unbroken chain. However, :hg:`fold` is the preferred command for this purpose. See :hg:`help fold` for more information. .. container:: verbose Some examples: - Edit the commit message for the current commit:: hg metaedit - Change the username for the current commit:: hg metaedit --user 'New User <*****@*****.**>' """ revs = list(revs) revs.extend(opts["rev"]) if not revs: if opts["fold"]: raise error.Abort(_("revisions must be specified with --fold")) revs = ["."] with repo.wlock(), repo.lock(): revs = scmutil.revrange(repo, revs) msgmap = { } # {node: message}, predefined messages, currently used by --batch usermap = { } # {node: author}, predefined authors, used by --jsoninputfile if opts["fold"]: root, head = fold._foldcheck(repo, revs) else: if repo.revs("%ld and public()", revs): raise error.Abort( _("cannot edit commit information for public " "revisions")) root = head = repo[revs.first()] wctx = repo[None] p1 = wctx.p1() tr = repo.transaction("metaedit") newp1 = None try: commitopts = opts.copy() allctx = [repo[r] for r in revs] jsoninputfile = None if any( commitopts.get(name) for name in ["message", "logfile", "reuse_message"]): commitopts["edit"] = False else: if opts["fold"]: msgs = [ _("HG: This is a fold of %d changesets.") % len(allctx) ] msgs += [ _("HG: Commit message of %s.\n\n%s\n") % (nodemod.short(c.node()), c.description()) for c in allctx ] else: if opts["batch"] and len(revs) > 1: msgmap = editmessages(repo, revs) msgs = [head.description()] jsoninputfile = opts.get("json_input_file") if jsoninputfile: try: if cmdutil.isstdiofilename(jsoninputfile): inputjson = pycompat.decodeutf8(ui.fin.read()) else: inputjson = pycompat.decodeutf8( util.readfile(jsoninputfile)) msgusermap = json.loads(inputjson) except IOError as inst: raise error.Abort( _("can't read JSON input file '%s': %s") % (jsoninputfile, encoding.strtolocal(inst.strerror))) except ValueError as inst: raise error.Abort( _("can't decode JSON input file '%s': %s") % (jsoninputfile, str(inst))) if not isinstance(msgusermap, dict): raise error.Abort( _("JSON input is not a dictionary (see --help for input format)" )) try: msgmap = { bin(node): msguser.get("message") for (node, msguser) in msgusermap.items() if "message" in msguser } usermap = { bin(node): msguser.get("user") for (node, msguser) in msgusermap.items() if "user" in msguser } except TypeError: raise error.Abort(_("invalid JSON input")) commitopts["message"] = "\n".join(msgs) commitopts["edit"] = True if root == head: # fast path: use metarewrite replacemap = {} # adding commitopts to the revisions to metaedit allctxopt = [{ "ctx": ctx, "commitopts": commitopts } for ctx in allctx] # all descendats that can be safely rewritten newunstable = common.newunstable(repo, revs) newunstableopt = [{ "ctx": ctx } for ctx in [repo[r] for r in newunstable]] # we need to edit descendants with the given revisions to not to # corrupt the stacks if _histediting(repo): ui.note( _("during histedit, the descendants of " "the edited commit weren't auto-rebased\n")) else: allctxopt += newunstableopt # we need topological order for all if mutation.enabled(repo): allctxopt = mutation.toposort( repo, allctxopt, nodefn=lambda copt: copt["ctx"].node()) else: allctxopt = sorted(allctxopt, key=lambda copt: copt["ctx"].rev()) def _rewritesingle(c, _commitopts): # Predefined message overrides other message editing choices. msg = msgmap.get(c.node()) if jsoninputfile: _commitopts["edit"] = False if msg is not None: _commitopts["message"] = msg _commitopts["edit"] = False user = usermap.get(c.node()) if user is not None: _commitopts["user"] = user if _commitopts.get("edit", False): msg = "HG: Commit message of changeset %s\n%s" % ( str(c), c.description(), ) _commitopts["message"] = msg bases = [ replacemap.get(c.p1().node(), c.p1().node()), replacemap.get(c.p2().node(), c.p2().node()), ] newid, created = common.metarewrite(repo, c, bases, commitopts=_commitopts) if created: replacemap[c.node()] = newid for copt in allctxopt: _rewritesingle( copt["ctx"], copt.get("commitopts", {"date": commitopts.get("date") or None}), ) if p1.node() in replacemap: repo.setparents(replacemap[p1.node()]) if len(replacemap) > 0: mapping = dict( map( lambda oldnew: (oldnew[0], [oldnew[1]]), pycompat.iteritems(replacemap), )) templ.setprop("nodereplacements", mapping) scmutil.cleanupnodes(repo, mapping, "metaedit") # TODO: set poroper phase boundaries (affects secret # phase only) else: ui.status(_("nothing changed\n")) return 1 else: # slow path: create a new commit targetphase = max(c.phase() for c in allctx) # TODO: if the author and message are the same, don't create a # new hash. Right now we create a new hash because the date can # be different. newid, created = common.rewrite( repo, root, allctx, head, [root.p1().node(), root.p2().node()], commitopts=commitopts, mutop="metaedit", ) if created: if p1.rev() in revs: newp1 = newid phases.retractboundary(repo, tr, targetphase, [newid]) mapping = dict([(repo[rev].node(), [newid]) for rev in revs]) templ.setprop("nodereplacements", mapping) scmutil.cleanupnodes(repo, mapping, "metaedit") else: ui.status(_("nothing changed\n")) return 1 tr.close() finally: tr.release() if opts["fold"]: ui.status(_("%i changesets folded\n") % len(revs)) if newp1 is not None: hg.update(repo, newp1)
def _summarize(repo, workingfilectx, otherctx, basectx): origfile = ( None if workingfilectx.isabsent() else scmutil.origpath(repo.ui, repo, repo.wjoin(workingfilectx.path())) ) def flags(context): if isinstance(context, absentfilectx): return { "contents": None, "exists": False, "isexec": None, "issymlink": None, } return { "contents": pycompat.decodeutf8(context.data()), "exists": True, "isexec": context.isexec(), "issymlink": context.islink(), } output = flags(workingfilectx) filestat = util.filestat.frompath(origfile) if origfile is not None else None if origfile and filestat.stat: # Since you can start a merge with a dirty working copy (either via # `up` or `merge -f`), "local" must reflect that, not the underlying # changeset. Those contents are available in the .orig version, so we # look there and mock up the schema to look like the other contexts. # # Test cases affected in test-merge-conflict-cornercases.t: #0 local = { "contents": pycompat.decodeutf8(util.readfile(origfile)), "exists": True, "isexec": util.isexec(origfile), "issymlink": util.statislink(filestat.stat), } else: # No backup file. This happens whenever the merge was esoteric enough # that we didn't launch a merge tool*, and instead prompted the user to # "use (c)hanged version, (d)elete, or leave (u)nresolved". # # The only way to exit that prompt with a conflict is to choose "u", # which leaves the local version in the working copy (with all its # pre-merge properties including any local changes), so we can reuse # that. # # Affected test cases: #0b, #1, #6, #11, and #12. # # Another alternative might be to use repo['.'][path] but that wouldn't # have any dirty pre-merge changes. # # *If we had, we'd've we would've overwritten the working copy, made a # backup and hit the above case. # # Copy, so the addition of the `path` key below does not affect both # versions. local = copy.copy(output) output["path"] = repo.wjoin(workingfilectx.path()) return { "base": flags(basectx), "local": local, "other": flags(otherctx), "output": output, "path": workingfilectx.path(), }