#!/usr/bin/env vpython3
# Copyright 2024 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import argparse
import logging
import re
import sys

from pathlib import Path

# Add depot tools to the sys path, for gerrit_util
BASE_PATH = Path(__file__).resolve().parent.parent.parent
DEPOT_TOOLS_PATH = BASE_PATH / 'third_party' / 'depot_tools'
sys.path.append(str(DEPOT_TOOLS_PATH))

import gerrit_util

GERRIT_HOST = 'chromium-review.googlesource.com'

TAGS_TO_STRIP = [
    "Change-Id",
    "Reviewed-on",
    "Reviewed-by",
    "Commit-Queue",
    "Cr-Commit-Position",
    "Auto-Submit",
]


# TODO(leszeks): Upstream to gerrit_util
def CherryPickWithMessage(host,
                          change,
                          destination,
                          revision='current',
                          message=None):
  """Create a cherry-pick commit from the given change, onto the given
    destination.
    """
  path = 'changes/%s/revisions/%s/cherrypick' % (change, revision)
  body = {'destination': destination, 'allow_conflicts': True}
  if message is not None:
    body['message'] = message
  conn = gerrit_util.CreateHttpConn(host, path, reqtype='POST', body=body)
  return gerrit_util.ReadHttpJsonResponse(conn)


def main(sys_args=None):
  sys_args = sys_args or sys.argv[1:]
  parser = argparse.ArgumentParser(
      description="Use the gerrit API to cherry-pick a revision onto a branch")
  parser.add_argument(
      "-r",
      "--reviewer",
      help="The author email used for code review (defaults to reviewers of the"
      "original CL). Can specify multiple reviewers. Appends @chromium.org if "
      "not specified.",
      default=[],
      nargs="*")
  parser.add_argument(
      "-b",
      "--branch",
      help="The branch to merge to (e.g. 12.0).",
      required=True)
  # TODO(leszeks): Add support for more than one revision. This will need to
  # cherry pick one of them using the gerrit API, then somehow applying the rest
  # onto it as additional patches.
  parser.add_argument("revision", nargs=1, help="The revision to merge.")

  options = parser.parse_args(sys_args)

  revision = options.revision[0]

  branch = options.branch
  if not re.match(r"[0-9]+\.[0-9]+", branch):
    print("Branch is not of the form 1.2")
    return 1
  branch_rev = f'refs/branch-heads/{branch}'

  reviewers = []
  for reviewer in options.reviewer:
    if not re.match(r".+@.+\..+", reviewer):
      reviewer = f"{reviewer}@chromium.org"
    reviewers.append(reviewer)

  print(f"Cherry-picking {revision} onto {branch_rev}")

  # Create the commit message, using the new version and information about the
  # original commit.
  print("Creating commit message...")
  original_commit = gerrit_util.GetChangeCommit(GERRIT_HOST, revision)

  commit_msg = original_commit['message'].splitlines()
  if not reviewers:
    for line in commit_msg:
      m = re.match(r"^Reviewed-by:\s*(.+)$", line)
      if m:
        reviewers.append(m.group(1))
    print(f"Picking reviewers from original commit: {','.join(reviewers)}")

  commit_msg[0] = f'Merged: {commit_msg[0]}'
  commit_msg[1:] = [
      line for line in commit_msg[1:]
      if not re.match(r"^(" + "|".join(TAGS_TO_STRIP) + r")\s*:", line)
  ]
  commit_msg.append(f"(cherry picked from commit {original_commit['commit']})")

  print("New commit message:")
  for line in commit_msg:
    print("| " + line)

  # Create a cherry pick commit from the original commit.
  cherry_pick = CherryPickWithMessage(
      GERRIT_HOST, revision, branch_rev, message="\n".join(commit_msg))
  print(cherry_pick)
  # Use the cherry pick number to refer to it, rather than the 'id', because
  # cherry picks end up having the same Change-Id as the original CL.
  cherry_pick_id = cherry_pick['_number']
  print(
      f"Created cherry-pick: https://{GERRIT_HOST}/c/{cherry_pick['_number']}")

  has_conflicts = cherry_pick.get('contains_git_conflicts', False)
  if has_conflicts:
    print("==================================================")
    print("WARNING: Cherry-pick created with merge conflicts.")
    print("Resolve locally with:")
    print(
        f"  git cl patch --branch merge-{cherry_pick_id} --force {cherry_pick_id}"
    )
    print(
        f"  git fetch --refmap='' origin refs/branch-heads/{branch}:refs/remotes/branch-heads/{branch}"
    )
    print(f"  git branch --set-upstream-to branch-heads/{branch}")
    print("==================================================")

  # Set Auto-Submit +1
  print("Setting 'Auto-Submit +1'...")
  try:
    gerrit_util.SetReview(
        GERRIT_HOST,
        cherry_pick_id,
        labels={"Auto-Submit": 1},
        ready=not has_conflicts)
  except:
    logging.warning("Could not set Auto-Submit +1")

  print(f"Adding {','.join(reviewers)} as reviewer(s)...")
  gerrit_util.AddReviewers(
      GERRIT_HOST,
      cherry_pick_id,
      reviewers=reviewers,
      notify=not has_conflicts)

  print("Done.")


if __name__ == "__main__":  # pragma: no cover
  sys.exit(main())
