| Index: scripts/slave/bot_update.py
 | 
| diff --git a/scripts/slave/bot_update.py b/scripts/slave/bot_update.py
 | 
| index 53c281a7157c423491f755181f87132a07c02279..4365d912b2aa4a7444ed69f2f830da2a01b473a4 100644
 | 
| --- a/scripts/slave/bot_update.py
 | 
| +++ b/scripts/slave/bot_update.py
 | 
| @@ -5,6 +5,8 @@
 | 
|  
 | 
|  import codecs
 | 
|  import copy
 | 
| +import cStringIO
 | 
| +import json
 | 
|  import optparse
 | 
|  import os
 | 
|  import pprint
 | 
| @@ -13,6 +15,7 @@ import socket
 | 
|  import subprocess
 | 
|  import sys
 | 
|  import time
 | 
| +import urllib2
 | 
|  import urlparse
 | 
|  
 | 
|  import os.path as path
 | 
| @@ -116,6 +119,15 @@ CACHE_DIR = path.join(SLAVE_DIR, 'cache_dir')
 | 
|  if sys.platform.startswith('win'):
 | 
|    CACHE_DIR = CACHE_DIR.replace('\\', '\\\\')
 | 
|  
 | 
| +# Find the patch tool.
 | 
| +ROOT_BUILD_DIR = path.dirname(SLAVE_DIR)
 | 
| +ROOT_B_DIR = path.dirname(ROOT_BUILD_DIR)
 | 
| +BUILD_INTERNAL_DIR = path.join(ROOT_B_DIR, 'build_internal')
 | 
| +if sys.platform.startswith('win'):
 | 
| +  PATCH_TOOL = path.join(BUILD_INTERNAL_DIR, 'tools', 'patch.EXE')
 | 
| +else:
 | 
| +  PATCH_TOOL = '/usr/bin/patch'
 | 
| +
 | 
|  
 | 
|  class SubprocessFailed(Exception):
 | 
|    def __init__(self, message, code):
 | 
| @@ -127,11 +139,18 @@ def call(*args, **kwargs):
 | 
|    """Interactive subprocess call."""
 | 
|    kwargs['stdout'] = subprocess.PIPE
 | 
|    kwargs['stderr'] = subprocess.STDOUT
 | 
| +  stdin_data = kwargs.pop('stdin_data', None)
 | 
| +  if stdin_data:
 | 
| +    kwargs['stdin'] = subprocess.PIPE
 | 
| +  out = cStringIO.StringIO()
 | 
|    for attempt in xrange(RETRIES):
 | 
|      attempt_msg = ' (retry #%d)' % attempt if attempt else ''
 | 
|      print '===Running %s%s===' % (' '.join(args), attempt_msg)
 | 
|      start_time = time.time()
 | 
|      proc = subprocess.Popen(args, **kwargs)
 | 
| +    if stdin_data:
 | 
| +      proc.stdin.write(stdin_data)
 | 
| +      proc.stdin.close()
 | 
|      # This is here because passing 'sys.stdout' into stdout for proc will
 | 
|      # produce out of order output.
 | 
|      while True:
 | 
| @@ -139,12 +158,13 @@ def call(*args, **kwargs):
 | 
|        if not buf:
 | 
|          break
 | 
|        sys.stdout.write(buf)
 | 
| +      out.write(buf)
 | 
|      code = proc.wait()
 | 
|      elapsed_time = ((time.time() - start_time) / 60.0)
 | 
|      if not code:
 | 
|        print '===Succeeded in %.1f mins===' % elapsed_time
 | 
|        print
 | 
| -      return 0
 | 
| +      return out.getvalue()
 | 
|      print '===Failed in %.1f mins===' % elapsed_time
 | 
|      print
 | 
|  
 | 
| @@ -160,7 +180,7 @@ def git(*args, **kwargs):
 | 
|    if sys.platform.startswith('win'):
 | 
|      git_executable += '.bat'
 | 
|    cmd = (git_executable,) + args
 | 
| -  call(*cmd, **kwargs)
 | 
| +  return call(*cmd, **kwargs)
 | 
|  
 | 
|  
 | 
|  def get_gclient_spec(solutions):
 | 
| @@ -284,10 +304,84 @@ def gclient_sync():
 | 
|         '--nohooks', '--noprehooks')
 | 
|  
 | 
|  
 | 
| +def create_less_than_or_equal_regex(number):
 | 
| +  """ Return a regular expression to test whether an integer less than or equal
 | 
| +      to 'number' is present in a given string.
 | 
| +  """
 | 
| +
 | 
| +  # In three parts, build a regular expression that match any numbers smaller
 | 
| +  # than 'number'.
 | 
| +  # For example, 78656 would give a regular expression that looks like:
 | 
| +  # Part 1
 | 
| +  # (78356|            # 78356
 | 
| +  # Part 2
 | 
| +  #  7835[0-5]|        # 78350-78355
 | 
| +  #  783[0-4][0-9]|    # 78300-78349
 | 
| +  #  78[0-2][0-9]{2}|  # 78000-78299
 | 
| +  #  7[0-7][0-9]{3}|   # 70000-77999
 | 
| +  #  [0-6][0-9]{4}|    # 10000-69999
 | 
| +  # Part 3
 | 
| +  #  [0-9]{1,4}        # 0-9999
 | 
| +
 | 
| +  # Part 1: Create an array with all the regexes, as described above.
 | 
| +  # Prepopulate it with the number itself.
 | 
| +  number = str(number)
 | 
| +  expressions = [number]
 | 
| +
 | 
| +  # Convert the number to a list, so we can translate digits in it to
 | 
| +  # expressions.
 | 
| +  num_list = list(number)
 | 
| +  num_len = len(num_list)
 | 
| +
 | 
| +  # Part 2: Go through all the digits in the number, starting from the end.
 | 
| +  # Each iteration appends a line to 'expressions'.
 | 
| +  for index in range (num_len - 1, -1, -1):
 | 
| +    # Convert this digit back to an integer.
 | 
| +    digit = int(num_list[index])
 | 
| +
 | 
| +    # Part 2.1: No processing if this digit is a zero.
 | 
| +    if digit == 0:
 | 
| +      continue
 | 
| +
 | 
| +    # Part 2.2: We switch the current digit X by a range "[0-(X-1)]".
 | 
| +    if digit == 1:
 | 
| +      num_list[index] = '0'
 | 
| +    else:
 | 
| +      num_list[index] = '[0-%d]' % (digit - 1)
 | 
| +
 | 
| +    # Part 2.3: We set all following digits to be "[0-9]".
 | 
| +    # Since we just decrementented a digit in a most important position, all
 | 
| +    # following digits don't matter. The possible numbers will always be smaller
 | 
| +    # than before we decremented.
 | 
| +    if (index + 1) < num_len:
 | 
| +      if (num_len - (index + 1)) == 1:
 | 
| +        num_list[index + 1] = '[0-9]'
 | 
| +      else:
 | 
| +        num_list[index + 1] = '[0-9]{%s}' % (num_len - (index + 1))
 | 
| +
 | 
| +    # Part 2.4: Add this new sub-expression to the list.
 | 
| +    expressions.append(''.join(num_list[:min(index+2, num_len)]))
 | 
| +
 | 
| +  # Part 3: We add all the full ranges to match all numbers that are at least
 | 
| +  # one order of magnitude smaller than the original numbers.
 | 
| +  if num_len == 2:
 | 
| +    expressions.append('[0-9]')
 | 
| +  elif num_len > 2:
 | 
| +    expressions.append('[0-9]{1,%s}' % (num_len - 1))
 | 
| +
 | 
| +  # All done. We now have our final regular expression.
 | 
| +  regex = '(%s)' % ('|'.join(expressions))
 | 
| +  return regex
 | 
| +
 | 
| +
 | 
|  def get_git_hash(revision, dir_name):
 | 
| -  match = "^git-svn-id: [^ ]*@%d" % revision
 | 
| -  cmd = ['git', 'log', '--grep', match, '--format=%H', dir_name]
 | 
| -  return subprocess.check_output(cmd).strip() or None
 | 
| +  match = "^git-svn-id: [^ ]*@%s " % create_less_than_or_equal_regex(revision)
 | 
| +  cmd = ['git', 'log', '-E', '--grep', match, '--format=%H', '--max-count=1']
 | 
| +  results = call(*cmd, cwd=dir_name).strip().splitlines()
 | 
| +  if results:
 | 
| +    return results[0]
 | 
| +  raise Exception('We can\'t resolve svn revision %s into a git hash' %
 | 
| +                  revision)
 | 
|  
 | 
|  
 | 
|  def deps2git(sln_dirs):
 | 
| @@ -303,6 +397,9 @@ def deps2git(sln_dirs):
 | 
|           '--deps=%s' % deps_file, '--out=%s' % deps_git_file)
 | 
|  
 | 
|  
 | 
| +def emit_got_revision(revision):
 | 
| +  print '@@@SET_BUILD_PROPERTY@got_revision@%s@@@' % revision
 | 
| +
 | 
|  def git_checkout(solutions, revision):
 | 
|    build_dir = os.getcwd()
 | 
|    # Revision only applies to the first solution.
 | 
| @@ -331,21 +428,55 @@ def git_checkout(solutions, revision):
 | 
|      git('pull', 'origin', 'master', cwd=sln_dir)
 | 
|      # TODO(hinoka): We probably have to make use of revision mapping.
 | 
|      if first_solution and revision and revision.lower() != 'head':
 | 
| +      emit_got_revision(revision)
 | 
|        if revision and revision.isdigit() and len(revision) < 40:
 | 
|          # rev_num is really a svn revision number, convert it into a git hash.
 | 
| -        git_ref = get_git_hash(revision, name)
 | 
| +        git_ref = get_git_hash(int(revision), name)
 | 
|        else:
 | 
|          # rev_num is actually a git hash or ref, we can just use it.
 | 
|          git_ref = revision
 | 
|        git('checkout', git_ref, cwd=sln_dir)
 | 
|      else:
 | 
|        git('checkout', 'origin/master', cwd=sln_dir)
 | 
| +      if first_solution:
 | 
| +        git_ref = git('log', '--format=%H', '--max-count=1',
 | 
| +                      cwd=sln_dir).strip()
 | 
|  
 | 
|      first_solution = False
 | 
| +  return git_ref
 | 
| +
 | 
| +
 | 
| +def _download(url):
 | 
| +  """Fetch url and return content, with retries for flake."""
 | 
| +  for attempt in xrange(RETRIES):
 | 
| +    try:
 | 
| +      return urllib2.urlopen(url).read()
 | 
| +    except Exception:
 | 
| +      if attempt == RETRIES - 1:
 | 
| +        raise
 | 
| +
 | 
|  
 | 
| +def apply_issue_svn(root, patch_url):
 | 
| +  patch_data = call('svn', 'cat', patch_url)
 | 
| +  call(PATCH_TOOL, '-p0', '--remove-empty-files', '--force', '--forward',
 | 
| +       stdin_data=patch_data, cwd=root)
 | 
|  
 | 
| -def apply_issue(issue, patchset, root, server):
 | 
| -  pass
 | 
| +
 | 
| +def apply_issue_rietveld(issue, patchset, root, server, rev_map, revision):
 | 
| +  apply_issue_bin = ('apply_issue.bat' if sys.platform.startswith('win')
 | 
| +                     else 'apply_issue')
 | 
| +  rev_map = json.loads(rev_map)
 | 
| +  if root in rev_map and rev_map[root] == 'got_revision':
 | 
| +    rev_map[root] = revision
 | 
| +  call(apply_issue_bin,
 | 
| +       '--root_dir', root,
 | 
| +       '--issue', issue,
 | 
| +       '--patchset', patchset,
 | 
| +       '--no-auth',
 | 
| +       '--server', server,
 | 
| +       '--revision-mapping', json.dumps(rev_map),
 | 
| +       '--base_ref', revision,
 | 
| +       '--force')
 | 
|  
 | 
|  
 | 
|  def check_flag(flag_file):
 | 
| @@ -374,14 +505,15 @@ def parse_args():
 | 
|                     help='Patchset from issue to patch from, if applicable.')
 | 
|    parse.add_option('--patch_url', help='Optional URL to SVN patch.')
 | 
|    parse.add_option('--root', help='Repository root.')
 | 
| -  parse.add_option('--rietveld_server', help='Rietveld server.')
 | 
| +  parse.add_option('--rietveld_server',
 | 
| +                   default='codereview.chromium.org',
 | 
| +                   help='Rietveld server.')
 | 
|    parse.add_option('--specs', help='Gcilent spec.')
 | 
|    parse.add_option('--master', help='Master name.')
 | 
|    parse.add_option('-f', '--force', action='store_true',
 | 
|                     help='Bypass check to see if we want to be run. '
 | 
|                          'Should ONLY be used locally.')
 | 
| -  # TODO(hinoka): We don't actually use this yet, we should factor this in.
 | 
| -  parse.add_option('--revision-mapping')
 | 
| +  parse.add_option('--revision_mapping')
 | 
|    parse.add_option('--revision')
 | 
|    parse.add_option('--slave_name', default=socket.getfqdn().split('.')[0],
 | 
|                     help='Hostname of the current machine, '
 | 
| @@ -437,11 +569,15 @@ def main():
 | 
|    # Calling git directory because there is no way to run Gclient without
 | 
|    # invoking DEPS.
 | 
|    print 'Fetching Git checkout'
 | 
| -  git_checkout(git_solutions, options.revision)
 | 
| -
 | 
| -  # TODO(hinoka): This must be implemented before we can turn this on for TS.
 | 
| -  # if options.issue:
 | 
| -  #   apply_issue(options.issue, options.patchset, options.root, options.server)
 | 
| +  got_revision = git_checkout(git_solutions, options.revision)
 | 
| +
 | 
| +  options.root =  options.root or dir_names[0]
 | 
| +  if options.patch_url:
 | 
| +    apply_issue_svn(options.root, options.patch_url)
 | 
| +  elif options.issue:
 | 
| +    apply_issue_rietveld(options.issue, options.patchset, options.root,
 | 
| +                         options.rietveld_server, options.revision_mapping,
 | 
| +                         got_revision)
 | 
|  
 | 
|    # Magic to get deps2git to work with internal DEPS.
 | 
|    shutil.copyfile(S2G_INTERNAL_FROM_PATH, S2G_INTERNAL_DEST_PATH)
 | 
| 
 |