def test_short_node(): assert (helpers.short_node("b016b6080ff9fa6d9ac459950e24bdcdaa939be0") == "b016b6080ff9") assert (helpers.short_node("this-is-not-a-sha-this-is-not-a-sha-test") == "this-is-not-a-sha-this-is-not-a-sha-test") assert helpers.short_node("b016b6080ff9") == "b016b6080ff9" assert helpers.short_node("b016b60") == "b016b60" assert helpers.short_node("mozilla-central") == "mozilla-central"
def patch(repo, args): """Patch repository from Phabricator's revisions. By default: * perform sanity checks * find the base commit * create a new branch/bookmark * apply the patches and commit the changes args.no_commit is True - no commit will be created after applying diffs args.apply_to - <head|tip|branch> (default: branch) branch - find base commit and apply on top of it head/tip - apply changes to current commit args.raw is True - only print out the diffs (--force doesn't change anything) Raises: * Error if uncommitted changes are present in the working tree * Error if Phabricator revision is not found * Error if `--apply-to base` and no base commit found in the first diff * Error if base commit not found in repository """ # Check if raw Conduit API can be used with wait_message("Checking connection to Phabricator."): # Check if raw Conduit API can be used if not conduit.check(): raise Error("Failed to use Conduit API") if not args.raw: # Check if local and remote VCS matches with wait_message("Checking VCS"): repo.check_vcs() # Look for any uncommitted changes with wait_message("Checking repository.."): clean = repo.is_worktree_clean() if not clean: raise Error( "Uncommitted changes present. Please %s them or commit before patching." % ("shelve" if isinstance(repo, Mercurial) else "stash")) # Get the target revision with wait_message("Fetching D%s.." % args.revision_id): revs = conduit.get_revisions(ids=[args.revision_id]) if not revs: raise Error("Revision not found") revision = revs[0] if not args.skip_dependencies: with wait_message("Fetching D%s children.." % args.revision_id): try: children = conduit.get_successor_phids( revision["phid"], include_abandoned=args.include_abandoned) non_linear = False except NonLinearException: children = [] non_linear = True patch_children = True if children: if args.yes or config.always_full_stack: patch_children = True else: children_msg = ("a child commit" if len(children) == 1 else "child commits") res = prompt( "Revision D%s has %s. Would you like to patch the " "full stack?." % (args.revision_id, children_msg), ["Yes", "No", "Always"], ) if res == "Always": config.always_full_stack = True config.write() patch_children = res == "Yes" or res == "Always" if patch_children: if non_linear and not args.yes: logger.warning( "Revision D%s has a non-linear successor graph.\n" "Unable to apply the full stack.", args.revision_id, ) res = prompt("Continue with only part of the stack?", ["Yes", "No"]) if res == "No": return # Get list of PHIDs in the stack try: with wait_message("Fetching D%s parents.." % args.revision_id): phids = conduit.get_ancestor_phids(revision["phid"]) except NonLinearException: raise Error( "Non linear dependency detected. Unable to patch the stack.") # Pull revisions data if phids: with wait_message("Fetching related revisions.."): revs.extend(conduit.get_revisions(phids=phids)) revs.reverse() if children and patch_children: with wait_message("Fetching related revisions.."): revs.extend(conduit.get_revisions(phids=children)) # Set the target id rev_id = revs[-1]["id"] if not args.raw: logger.info( "Patching revision%s: %s", "s" if len(revs) > 1 else "", " ".join(["D%s" % r["id"] for r in revs]), ) # Pull diffs with wait_message("Downloading patch information.."): diffs = conduit.get_diffs([r["fields"]["diffPHID"] for r in revs]) if not args.no_commit and not args.raw: for rev in revs: diff = diffs[rev["fields"]["diffPHID"]] if not diff["attachments"]["commits"]["commits"]: raise Error( "A diff without commit information detected in revision D%s.\n" "Use `--no-commit` to patch the working tree." % rev["id"]) base_node = None if not args.raw: args.apply_to = args.apply_to or config.apply_patch_to if args.apply_to == "base": base_node = get_base_ref(diffs[revs[0]["fields"]["diffPHID"]]) if not base_node: raise Error("Base commit not found in diff. " "Use `--apply-to here` to patch current commit.") elif args.apply_to != "here": base_node = args.apply_to if args.apply_to != "here": try: with wait_message("Checking %s.." % short_node(base_node)): base_node = repo.check_node(base_node) except NotFoundError as e: msg = "Unknown revision: %s" % short_node(base_node) if str(e): msg += "\n%s" % str(e) if args.apply_to == "base": msg += "\nUse --apply-to to set the base commit." raise Error(msg) branch_name = None if args.no_commit else "phab-D%s" % rev_id repo.before_patch(base_node, branch_name) parent = None for rev in revs: # Prepare the body using just the data from Phabricator body = prepare_body( rev["fields"]["title"], rev["fields"]["summary"], rev["id"], repo.phab_url, depends_on=parent, ) parent = rev["id"] diff = diffs[rev["fields"]["diffPHID"]] with wait_message("Downloading D%s.." % rev["id"]): raw = conduit.call("differential.getrawdiff", {"diffID": diff["id"]}) if args.no_commit: with wait_message("Applying D%s.." % rev["id"]): apply_patch(raw, repo.path) elif args.raw: logger.info(raw) else: diff_commits = diff["attachments"]["commits"]["commits"] author = "%s <%s>" % ( diff_commits[0]["author"]["name"], diff_commits[0]["author"]["email"], ) try: with wait_message("Applying D%s.." % rev["id"]): repo.apply_patch(raw, body, author, diff["fields"]["dateCreated"]) except subprocess.CalledProcessError: raise Error("Patch failed to apply") if not args.raw and rev["id"] != revs[-1]["id"]: logger.info("D%s applied", rev["id"]) if not args.raw: logger.warning("D%s applied", rev_id)