vendor: import repopick from android-7.1

Add repopick script from CM

patchset 4: cybojenix
  clean up the script:
    remove fetching from github
    cleaned up fetching locations of binaries
    if ANDROID_BUILD_TOP isn't defined, error out

PS 5:
  Added back the check if fetch succeeded.
  Fixed verbose print about fetching.

patchset 6:
  final changes
    fix detecting ANDROID_BUILD_TOP
    send all relavant data to stderr

- Add function description from LineageOS

Change-Id: If67c5f178d03fadd8b79b1908f4a9cb231efbb28

repopick: Add a way to checkout instead of cherrypick

This helps if you want to take a commit and its dependencies, and not
just a particular commit

Change-Id: Ib245cce560c7b0d6fd03198a8c69a13d4720a4cb

repopick: Allow picking batch of commits with the same Change-Id

Gerrit does allow assigning the same Change-Id for multiple commits. This patch allows picking them properly.

Change-Id: Idedc64f58eebe41a0d212c72329d15acff24efb9

Make repopick support gerrit 2.9

Change-Id: I5b5fcfdfb69fd8639a7170f68966817ba1bb98b7

repopick: skip a cherry-pick if its already been merged

Change-Id: I280b4945344e7483a192d1c9769c42b8fd2a2aef

repopick: open changes are either NEW or OPEN, not just OPEN

Gerrit's API is terrible. I believe it was written by monkeys.

Change-Id: I8ec97a1e4277b1ee89070976c5d0994ee178cf79

repopick: support specifying a range of commits to pick

* for example: repopick 12345-123450

Change-Id: I58e26125d3e8e836637ccd41d60cb56ab488e999

repopick: remove symbols from author and committer name

Change-Id: I3885ac89c2d1e2a72d8d95212faff00338ad3e3a

repopick: allow specifying a topic to pick all commits from

Change-Id: I4fb60120794a77986bf641de063a8d41f4f45a23

repopick: cleanup some redundancy

Change-Id: Ic1ad5e717d1bfc47cf8137cf2fe71bfd5d3456c7
diff --git a/build/tools/repopick.py b/build/tools/repopick.py
new file mode 100755
index 0000000..aa1d07b
--- /dev/null
+++ b/build/tools/repopick.py
@@ -0,0 +1,330 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2013 The CyanogenMod Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Run repopick.py -h for a description of this utility.
+#
+
+from __future__ import print_function
+
+import sys
+import json
+import os
+import subprocess
+import re
+import argparse
+import textwrap
+
+try:
+  # For python3
+  import urllib.request
+except ImportError:
+  # For python2
+  import imp
+  import urllib2
+  urllib = imp.new_module('urllib')
+  urllib.request = urllib2
+
+# Parse the command line
+parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent('''\
+    repopick.py is a utility to simplify the process of cherry picking
+    patches from OmniROM's Gerrit instance.
+
+    Given a list of change numbers, repopick will cd into the project path
+    and cherry pick the latest patch available.
+
+    With the --start-branch argument, the user can specify that a branch
+    should be created before cherry picking. This is useful for
+    cherry-picking many patches into a common branch which can be easily
+    abandoned later (good for testing other's changes.)
+
+    The --abandon-first argument, when used in conjuction with the
+    --start-branch option, will cause repopick to abandon the specified
+    branch in all repos first before performing any cherry picks.'''))
+parser.add_argument('change_number', nargs='*', help='change number to cherry pick')
+parser.add_argument('-i', '--ignore-missing', action='store_true', help='do not error out if a patch applies to a missing directory')
+parser.add_argument('-c', '--checkout', action='store_true', help='checkout instead of cherry pick')
+parser.add_argument('-s', '--start-branch', nargs=1, help='start the specified branch before cherry picking')
+parser.add_argument('-a', '--abandon-first', action='store_true', help='before cherry picking, abandon the branch specified in --start-branch')
+parser.add_argument('-b', '--auto-branch', action='store_true', help='shortcut to "--start-branch auto --abandon-first --ignore-missing"')
+parser.add_argument('-q', '--quiet', action='store_true', help='print as little as possible')
+parser.add_argument('-v', '--verbose', action='store_true', help='print extra information to aid in debug')
+parser.add_argument('-t', '--topic', help='pick all commits from a specified topic')
+args = parser.parse_args()
+if args.start_branch == None and args.abandon_first:
+    parser.error('if --abandon-first is set, you must also give the branch name with --start-branch')
+if args.auto_branch:
+    args.abandon_first = True
+    args.ignore_missing = True
+    if not args.start_branch:
+        args.start_branch = ['auto']
+if args.quiet and args.verbose:
+    parser.error('--quiet and --verbose cannot be specified together')
+if len(args.change_number) > 0 and args.topic:
+    parser.error('cannot specify a topic and change number(s) together')
+if len(args.change_number) == 0 and not args.topic:
+    parser.error('must specify at least one commit id or a topic')
+
+# Helper function to determine whether a path is an executable file
+def is_exe(fpath):
+    return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
+
+# Implementation of Unix 'which' in Python
+#
+# From: http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
+def which(program):
+    fpath, fname = os.path.split(program)
+    if fpath:
+        if is_exe(program):
+            return program
+    else:
+        for path in os.environ["PATH"].split(os.pathsep):
+            path = path.strip('"')
+            exe_file = os.path.join(path, program)
+            if is_exe(exe_file):
+                return exe_file
+    sys.stderr.write('ERROR: Could not find the %s program in $PATH\n' % program)
+    sys.exit(1)
+
+# Simple wrapper for os.system() that:
+#   - exits on error
+#   - prints out the command if --verbose
+#   - suppresses all output if --quiet
+def execute_cmd(cmd, exit_on_fail=True):
+    if args.verbose:
+        print('Executing: %s' % cmd)
+    if args.quiet:
+        cmd = cmd.replace(' && ', ' &> /dev/null && ')
+        cmd = cmd + " &> /dev/null"
+    ret = os.system(cmd)
+    if ret and exit_on_fail:
+        if not args.verbose:
+            sys.stderr.write('\nERROR: Command that failed:\n%s' % cmd)
+        sys.exit(1)
+    return ret
+
+# Verifies whether pathA is a subdirectory (or the same) as pathB
+def is_pathA_subdir_of_pathB(pathA, pathB):
+    pathA = os.path.realpath(pathA) + '/'
+    pathB = os.path.realpath(pathB) + '/'
+    return(pathB == pathA[:len(pathB)])
+
+# Find the necessary bins - repo
+repo_bin = which('repo')
+
+# Find the necessary bins - git
+git_bin = which('git')
+
+# Change current directory to the top of the tree
+if os.environ.get('ANDROID_BUILD_TOP', None):
+    top = os.environ['ANDROID_BUILD_TOP']
+    if not is_pathA_subdir_of_pathB(os.getcwd(), top):
+        sys.stderr.write('ERROR: You must run this tool from within $ANDROID_BUILD_TOP!\n')
+        sys.exit(1)
+    os.chdir(os.environ['ANDROID_BUILD_TOP'])
+else:
+    sys.stderr.write('ERROR: $ANDROID_BUILD_TOP is not defined. please check build/envsetup.sh\n')
+    sys.exit(1)
+
+# Sanity check that we are being run from the top level of the tree
+if not os.path.isdir('.repo'):
+    sys.stderr.write('ERROR: No .repo directory found. Please run this from the top of your tree.\n')
+    sys.exit(1)
+
+# If --abandon-first is given, abandon the branch before starting
+if args.abandon_first:
+    # Determine if the branch already exists; skip the abandon if it does not
+    plist = subprocess.Popen([repo_bin,"info"], stdout=subprocess.PIPE)
+    needs_abandon = False
+    while(True):
+        pline = plist.stdout.readline().rstrip()
+        if not pline:
+            break
+        matchObj = re.match(r'Local Branches.*\[(.*)\]', pline.decode())
+        if matchObj:
+            local_branches = re.split('\s*,\s*', matchObj.group(1))
+            if any(args.start_branch[0] in s for s in local_branches):
+                needs_abandon = True
+
+    if needs_abandon:
+        # Perform the abandon only if the branch already exists
+        if not args.quiet:
+            print('Abandoning branch: %s' % args.start_branch[0])
+        cmd = '%s abandon %s' % (repo_bin, args.start_branch[0])
+        execute_cmd(cmd)
+        if not args.quiet:
+            print('')
+
+# Get the list of projects that repo knows about
+#   - convert the project name to a project path
+project_name_to_path = {}
+plist = subprocess.Popen([repo_bin,"list"], stdout=subprocess.PIPE)
+project_path = None
+while(True):
+    pline = plist.stdout.readline().rstrip()
+    if not pline:
+        break
+    ppaths = re.split('\s*:\s*', pline.decode())
+    project_name_to_path[ppaths[1]] = ppaths[0]
+
+# Get all commits for a specified topic
+if args.topic:
+    url = 'http://gerrit.omnirom.org/changes/?q=topic:%s' % args.topic
+    if args.verbose:
+        print('Fetching all commits from topic: %s\n' % args.topic)
+    f = urllib.request.urlopen(url)
+    d = f.read().decode("utf-8")
+    if args.verbose:
+        print('Result from request:\n' + d)
+
+    # Clean up the result
+    d = d.lstrip(")]}'\n")
+    if re.match(r'\[\s*\]', d):
+        sys.stderr.write('ERROR: Topic %s was not found on the server\n' % args.topic)
+        sys.exit(1)
+    if args.verbose:
+        print('Result from request:\n' + d)
+
+    try:
+        data = json.loads(d)
+    except ValueError:
+        sys.stderr.write('ERROR: Could not load json')
+        sys.exit(1)
+
+    args.change_number = sorted(d['_number'] for d in data)
+
+# Check for range of commits and rebuild array
+changelist = []
+for change in args.change_number:
+    c=str(change)
+    if '-' in c:
+        templist = c.split('-')
+        for i in range(int(templist[0]), int(templist[1]) + 1):
+            changelist.append(str(i))
+    else:
+        changelist.append(c)
+
+args.change_number = changelist
+
+# Iterate through the requested change numbers
+for change in args.change_number:
+    if not args.quiet:
+        print('Applying change number %s ...' % change)
+
+    # Fetch information about the change from Gerrit's REST API
+    #
+    # gerrit returns two lines, a magic string and then valid JSON:
+    #   )]}'
+    #   [ ... valid JSON ... ]
+    url = 'https://gerrit.omnirom.org/changes/?q=%s&o=CURRENT_REVISION&o=CURRENT_COMMIT&pp=0' % change
+    if args.verbose:
+        print('Fetching from: %s\n' % url)
+    f = urllib.request.urlopen(url)
+    d = f.read().decode("utf-8")
+    if args.verbose:
+        print('Result from request:\n' + d)
+
+    # Clean up the result
+    d = d.split('\n')[1]
+    if re.match(r'\[\s*\]', d):
+        sys.stderr.write('ERROR: Change number %s was not found on the server\n' % change)
+        sys.exit(1)
+
+    # Parse the JSON
+    try:
+        data_array = json.loads(d)
+    except ValueError:
+        sys.stderr.write('ERROR: The response from the server could not be parsed properly\n')
+        if args.verbose:
+            sys.stderr.write('The malformed response was: %s\n' % d)
+        sys.exit(1)
+    # Enumerate through JSON response
+    for (i, data) in enumerate(data_array):
+        date_fluff       = '.000000000'
+        project_name     = data['project']
+        change_number    = data['_number']
+        current_revision = data['revisions'][data['current_revision']]
+        status           = data['status']
+        patch_number     = current_revision['_number']
+        # Backwards compatibility
+        if 'http' in current_revision['fetch']:
+            fetch_url        = current_revision['fetch']['http']['url']
+            fetch_ref        = current_revision['fetch']['http']['ref']
+        else:
+            fetch_url        = current_revision['fetch']['anonymous http']['url']
+            fetch_ref        = current_revision['fetch']['anonymous http']['ref']
+        author_name      = current_revision['commit']['author']['name']
+        author_email     = current_revision['commit']['author']['email']
+        author_date      = current_revision['commit']['author']['date'].replace(date_fluff, '')
+        committer_name   = current_revision['commit']['committer']['name']
+        committer_email  = current_revision['commit']['committer']['email']
+        committer_date   = current_revision['commit']['committer']['date'].replace(date_fluff, '')
+        subject          = current_revision['commit']['subject']
+
+        # remove symbols from committer name
+        # from http://stackoverflow.com/questions/9942594/unicodeencodeerror-ascii-codec-cant-encode-character-u-xa0-in-position-20?rq=1
+        author_name = author_name.encode('ascii', 'ignore').decode('ascii')
+        committer_name = committer_name.encode('ascii', 'ignore').decode('ascii')
+
+        # Check if commit is not open, skip it.
+        if (status != 'OPEN' and status != 'NEW'):
+            print("Change is not open. Skipping the cherry pick.")
+            continue;
+
+        # Convert the project name to a project path
+        #   - check that the project path exists
+        if project_name in project_name_to_path:
+            project_path = project_name_to_path[project_name];
+        elif args.ignore_missing:
+            print('WARNING: Skipping %d since there is no project directory for: %s\n' % (change_number, project_name))
+            continue;
+        else:
+            sys.stderr.write('ERROR: For %d, could not determine the project path for project %s\n' % (change_number, project_name))
+            continue;
+
+        # If --start-branch is given, create the branch (more than once per path is okay; repo ignores gracefully)
+        if args.start_branch:
+            cmd = '%s start %s %s' % (repo_bin, args.start_branch[0], project_path)
+            execute_cmd(cmd)
+
+        # Print out some useful info
+        if not args.quiet:
+            print('--> Subject:       "%s"' % subject)
+            print('--> Project path:  %s' % project_path)
+            print('--> Change number: %d (Patch Set %d)' % (change_number, patch_number))
+            print('--> Author:        %s <%s> %s' % (author_name, author_email, author_date))
+            print('--> Committer:     %s <%s> %s' % (committer_name, committer_email, committer_date))
+
+        if args.verbose:
+            print('Trying to fetch the change %d (Patch Set %d) from Gerrit')
+        cmd = 'cd %s && git fetch %s %s' % (project_path, fetch_url, fetch_ref)
+        execute_cmd(cmd)
+        # Check if it worked
+        FETCH_HEAD = '%s/.git/FETCH_HEAD' % project_path
+        if os.stat(FETCH_HEAD).st_size == 0:
+            # That didn't work, print error and exit
+            sys.stderr.write('ERROR: Fetching change from Gerrit failed. Exiting...')
+            continue;
+        # Perform the cherry-pick or checkout
+        if args.checkout:
+            cmd = 'cd %s && git checkout FETCH_HEAD' % (project_path)
+        else:
+            cmd = 'cd %s && git cherry-pick FETCH_HEAD' % (project_path)
+
+        execute_cmd(cmd)
+        if not args.quiet:
+            print('Change #%d (Patch Set %d) %s into %s' % (change_number, patch_number, 'checked out' if args.checkout else 'cherry-picked', project_path))