Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(350)

Side by Side Diff: recipe_engine/fetch.py

Issue 2829203002: [autoroller] All commits in updates(), only roll interesting ones. (Closed)
Patch Set: windows fix Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « recipe_engine/autoroll_impl/commit_list.py ('k') | recipe_engine/package.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2016 The LUCI Authors. All rights reserved. 1 # Copyright 2016 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file. 3 # that can be found in the LICENSE file.
4 4
5 import calendar 5 import calendar
6 import httplib 6 import httplib
7 import json 7 import json
8 import logging 8 import logging
9 import os 9 import os
10 import re 10 import re
(...skipping 13 matching lines...) Expand all
24 from . import env 24 from . import env
25 from . import requests_ssl 25 from . import requests_ssl
26 from .requests_ssl import requests 26 from .requests_ssl import requests
27 27
28 import subprocess42 28 import subprocess42
29 from google.protobuf import json_format 29 from google.protobuf import json_format
30 30
31 LOGGER = logging.getLogger(__name__) 31 LOGGER = logging.getLogger(__name__)
32 32
33 33
34 def has_interesting_changes(spec, changed_files):
35 # TODO(iannucci): analyze bundle_extra_paths.txt too.
36 return (
37 'infra/config/recipes.cfg' in changed_files or
38 any(f.startswith(spec.recipes_path) for f in changed_files)
39 )
40
41
34 class FetchError(Exception): 42 class FetchError(Exception):
35 pass 43 pass
36 44
37 45
38 class FetchNotAllowedError(FetchError): 46 class FetchNotAllowedError(FetchError):
39 pass 47 pass
40 48
41 49
42 class UnresolvedRefspec(Exception): 50 class UnresolvedRefspec(Exception):
43 pass 51 pass
44 52
45 53
46 # revision (str): the revision of this commit (i.e. hash) 54 # revision (str): the revision of this commit (i.e. hash)
47 # author_email (str|None): the email of the author of this commit 55 # author_email (str|None): the email of the author of this commit
48 # commit_timestamp (int): the unix commit timestamp for this commit 56 # commit_timestamp (int): the unix commit timestamp for this commit
49 # message_lines (tuple(str)): the message of this commit 57 # message_lines (tuple(str)): the message of this commit
50 # spec (package_pb2.Package): the parsed infra/config/recipes.cfg file or None. 58 # spec (package_pb2.Package): the parsed infra/config/recipes.cfg file or None.
59 # roll_candidate (bool): if this commit contains changes which are known to
60 # affect the behavior of the recipes (i.e. modifications within recipe_path
61 # and/or modifications to recipes.cfg)
51 CommitMetadata = namedtuple( 62 CommitMetadata = namedtuple(
52 '_CommitMetadata', 63 '_CommitMetadata',
53 'revision author_email commit_timestamp message_lines spec') 64 'revision author_email commit_timestamp message_lines spec roll_candidate')
54 65
55 66
56 class Backend(object): 67 class Backend(object):
57 @staticmethod 68 @staticmethod
58 def class_for_type(repo_type): 69 def class_for_type(repo_type):
59 """ 70 """
60 Args: 71 Args:
61 repo_type (package_pb2.DepSpec.RepoType) 72 repo_type (package_pb2.DepSpec.RepoType)
62 73
63 Returns Backend (class): Returns the Backend appropriate for the 74 Returns Backend (class): Returns the Backend appropriate for the
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
107 _GIT_METADATA_CACHE = {} 118 _GIT_METADATA_CACHE = {}
108 119
109 # This matches git commit hashes. 120 # This matches git commit hashes.
110 _COMMIT_RE = re.compile(r'^[a-fA-F0-9]{40}$') 121 _COMMIT_RE = re.compile(r'^[a-fA-F0-9]{40}$')
111 122
112 def commit_metadata(self, refspec): 123 def commit_metadata(self, refspec):
113 """Cached version of _commit_metadata_impl. 124 """Cached version of _commit_metadata_impl.
114 125
115 The refspec will be resolved if it's not absolute. 126 The refspec will be resolved if it's not absolute.
116 127
117 Returns { 128 Returns (CommitMetadata).
118 'author': '<author name>',
119 'message': '<commit message>',
120 'spec': package_pb2.Package or None, # the parsed recipes.cfg file.
121 }
122 """ 129 """
123 revision = self.resolve_refspec(refspec) 130 revision = self.resolve_refspec(refspec)
124 cache = self._GIT_METADATA_CACHE.setdefault(self.repo_url, {}) 131 cache = self._GIT_METADATA_CACHE.setdefault(self.repo_url, {})
125 if revision not in cache: 132 if revision not in cache:
126 cache[revision] = self._commit_metadata_impl(revision) 133 cache[revision] = self._commit_metadata_impl(revision)
127 return cache[revision] 134 return cache[revision]
128 135
129 @classmethod 136 @classmethod
130 def is_resolved_revision(cls, revision): 137 def is_resolved_revision(cls, revision):
131 return cls._COMMIT_RE.match(revision) 138 return cls._COMMIT_RE.match(revision)
132 139
133 @classmethod 140 @classmethod
134 def assert_resolved(cls, revision): 141 def assert_resolved(cls, revision):
135 if not cls.is_resolved_revision(revision): 142 if not cls.is_resolved_revision(revision):
136 raise UnresolvedRefspec('unresolved refspec %r' % revision) 143 raise UnresolvedRefspec('unresolved refspec %r' % revision)
137 144
138 def resolve_refspec(self, refspec): 145 def resolve_refspec(self, refspec):
139 if self.is_resolved_revision(refspec): 146 if self.is_resolved_revision(refspec):
140 return refspec 147 return refspec
141 return self._resolve_refspec_impl(refspec) 148 return self._resolve_refspec_impl(refspec)
142 149
143 def updates(self, revision, other_revision, paths): 150 def updates(self, revision, other_revision):
144 """Returns a list of revisions |revision| through |other_revision| 151 """Returns a list of revisions |revision| through |other_revision|
145 (inclusive). 152 (inclusive).
146 153
147 If |paths| is a non-empty list, the history is scoped just to these paths.
148
149 Returns list(CommitMetadata) - The commit metadata in the range 154 Returns list(CommitMetadata) - The commit metadata in the range
150 (revision,other_revision]. 155 (revision,other_revision].
151 """ 156 """
152 self.assert_resolved(revision) 157 self.assert_resolved(revision)
153 self.assert_resolved(other_revision) 158 self.assert_resolved(other_revision)
154 return self._updates_impl(revision, other_revision, paths) 159 return self._updates_impl(revision, other_revision)
155 160
156 ### direct overrides. These are public methods which must be overridden. 161 ### direct overrides. These are public methods which must be overridden.
157 162
158 @property 163 @property
159 def repo_type(self): 164 def repo_type(self):
160 """Returns package_pb2.DepSpec.RepoType.""" 165 """Returns package_pb2.DepSpec.RepoType."""
161 raise NotImplementedError() 166 raise NotImplementedError()
162 167
163 def fetch(self, refspec): 168 def fetch(self, refspec):
164 """Does a fetch for the provided refspec (e.g. get all data from remote), if 169 """Does a fetch for the provided refspec (e.g. get all data from remote), if
(...skipping 13 matching lines...) Expand all
178 remote git repo, e.g. 'refs/heads/master', 'deadbeef...face', etc. 183 remote git repo, e.g. 'refs/heads/master', 'deadbeef...face', etc.
179 """ 184 """
180 # TODO(iannucci): Alter the contract for this method so that it only checks 185 # TODO(iannucci): Alter the contract for this method so that it only checks
181 # out the files referred to according to the rules that the bundle 186 # out the files referred to according to the rules that the bundle
182 # subcommand uses. 187 # subcommand uses.
183 raise NotImplementedError() 188 raise NotImplementedError()
184 189
185 ### private overrides. Override these in the implementations, but don't call 190 ### private overrides. Override these in the implementations, but don't call
186 ### externally. 191 ### externally.
187 192
188 def _updates_impl(self, revision, other_revision, paths): 193 def _updates_impl(self, revision, other_revision):
189 """Returns a list of revisions |revision| through |other_revision|. This 194 """Returns a list of revisions |revision| through |other_revision|. This
190 includes |revision| and |other_revision|. 195 includes |revision| and |other_revision|.
191 196
192 If |paths| is a non-empty list, the history is scoped just to these paths.
193
194 Args: 197 Args:
195 revision (str) - the first git commit 198 revision (str) - the first git commit
196 other_revision (str) - the second git commit 199 other_revision (str) - the second git commit
197 200
198 Returns list(CommitMetadata) - The commit metadata in the range 201 Returns list(CommitMetadata) - The commit metadata in the range
199 [revision,other_revision]. 202 [revision,other_revision].
200 """ 203 """
201 raise NotImplementedError() 204 raise NotImplementedError()
202 205
203 def _resolve_refspec_impl(self, refspec): 206 def _resolve_refspec_impl(self, refspec):
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
238 def _git(self, *args): 241 def _git(self, *args):
239 """Runs a git command. 242 """Runs a git command.
240 243
241 Will automatically set low speed limit/time, and cd into the checkout_dir. 244 Will automatically set low speed limit/time, and cd into the checkout_dir.
242 245
243 Args: 246 Args:
244 *args (str) - The list of command arguments to pass to git. 247 *args (str) - The list of command arguments to pass to git.
245 248
246 Raises GitError on failure. 249 Raises GitError on failure.
247 """ 250 """
251 if self._GIT_BINARY.endswith('.bat'):
252 # On the esteemed Windows Operating System, '^' is an escape character.
253 # Since .bat files are running cmd.exe under the hood, they interpret this
254 # escape character. We need to ultimately get a single ^, so we need two
255 # ^'s for when we invoke the .bat, and each of those needs to be escaped
256 # when the bat ultimately invokes the git.exe binary. This leaves us with
257 # a total of 4x the ^'s that we originally wanted. Hooray.
258 args = [a.replace('^', '^^^^') for a in args]
259
248 cmd = [ 260 cmd = [
249 self._GIT_BINARY, 261 self._GIT_BINARY,
250 '-C', self.checkout_dir, 262 '-C', self.checkout_dir,
251 ] + list(args) 263 ] + list(args)
252 264
253 try: 265 try:
254 return self._execute(*cmd) 266 return self._execute(*cmd)
255 except subprocess42.CalledProcessError as e: 267 except subprocess42.CalledProcessError as e:
256 subcommand = (args[0]) if args else ('') 268 subcommand = (args[0]) if args else ('')
257 raise GitError('Git "%s" failed: %s' % (subcommand, e.message,)) 269 raise GitError('Git "%s" failed: %s' % (subcommand, e.message,))
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
320 self.fetch(refspec) 332 self.fetch(refspec)
321 333
322 # reset touches index.lock which is problematic when multiple processes are 334 # reset touches index.lock which is problematic when multiple processes are
323 # accessing the recipes at the same time. To allieviate this, we do a quick 335 # accessing the recipes at the same time. To allieviate this, we do a quick
324 # diff, which will exit if `revision` is not already checked out. 336 # diff, which will exit if `revision` is not already checked out.
325 try: 337 try:
326 self._git('diff', '--quiet', revision) 338 self._git('diff', '--quiet', revision)
327 except GitError: 339 except GitError:
328 self._git('reset', '-q', '--hard', revision) 340 self._git('reset', '-q', '--hard', revision)
329 341
330 def _updates_impl(self, revision, other_revision, paths): 342 def _updates_impl(self, revision, other_revision):
331 args = [ 343 args = [
332 'rev-list', 344 'rev-list',
333 '--reverse', 345 '--reverse',
334 '--topo-order', 346 '--topo-order',
335 '%s..%s' % (revision, other_revision), 347 '%s..%s' % (revision, other_revision),
336 ] 348 ]
337 if paths:
338 args.extend(['--'] + paths)
339 return [ 349 return [
340 self.commit_metadata(rev) 350 self.commit_metadata(rev)
341 for rev in self._git(*args).strip().split('\n') 351 for rev in self._git(*args).strip().split('\n')
342 if bool(rev) 352 if bool(rev)
343 ] 353 ]
344 354
345 def _resolve_refspec_impl(self, revision): 355 def _resolve_refspec_impl(self, revision):
346 self._ensure_local_repo_exists() 356 self._ensure_local_repo_exists()
347 self.assert_remote('resolve refspec %r' % revision) 357 self.assert_remote('resolve refspec %r' % revision)
348 rslt = self._git('ls-remote', self.repo_url, revision).split()[0] 358 rslt = self._git('ls-remote', self.repo_url, revision).split()[0]
(...skipping 11 matching lines...) Expand all
360 # %`Body` 370 # %`Body`
361 meta = self._git( 371 meta = self._git(
362 'show', '-s', '--format=%aE%n%ct%n%B', revision).rstrip('\n').splitlines() 372 'show', '-s', '--format=%aE%n%ct%n%B', revision).rstrip('\n').splitlines()
363 373
364 try: 374 try:
365 spec = package_io.parse(self._git( 375 spec = package_io.parse(self._git(
366 'cat-file', 'blob', '%s:infra/config/recipes.cfg' % revision)) 376 'cat-file', 'blob', '%s:infra/config/recipes.cfg' % revision))
367 except GitError: 377 except GitError:
368 spec = None 378 spec = None
369 379
380 # check diff to see if it touches anything interesting.
381 changed_files = set(self._git(
382 'diff-tree', '-r', '--no-commit-id', '--name-only', '%s^!' % revision)
383 .splitlines())
384
370 return CommitMetadata(revision, meta[0], 385 return CommitMetadata(revision, meta[0],
371 int(meta[1]), tuple(meta[2:]), 386 int(meta[1]), tuple(meta[2:]),
372 spec) 387 spec, has_interesting_changes(spec, changed_files))
373 388
374 class GitilesFetchError(FetchError): 389 class GitilesFetchError(FetchError):
375 """An HTTP error that occurred during Gitiles fetching.""" 390 """An HTTP error that occurred during Gitiles fetching."""
376 391
377 def __init__(self, status, message): 392 def __init__(self, status, message):
378 super(GitilesFetchError, self).__init__( 393 super(GitilesFetchError, self).__init__(
379 'Gitiles error code (%d): %s' % (status, message)) 394 'Gitiles error code (%d): %s' % (status, message))
380 self.status = status 395 self.status = status
381 self.message = message 396 self.message = message
382 397
383 @staticmethod 398 @staticmethod
384 def transient(e): 399 def transient(e):
385 """ 400 """
386 Returns (bool): True if "e" is a GitilesFetchError with transient HTTP code. 401 Returns (bool): True if "e" is a GitilesFetchError with transient HTTP code.
387 """ 402 """
388 return (isinstance(e, GitilesFetchError) and 403 return (isinstance(e, GitilesFetchError) and
389 e.status >= httplib.INTERNAL_SERVER_ERROR) 404 e.status >= httplib.INTERNAL_SERVER_ERROR)
390 405
391 406
392 # Internal cache object for GitilesBackend. 407 # Internal cache object for GitilesBackend.
393 # commit (str) - the git commit hash 408 # commit (str) - the git commit hash
394 # author_email (str) - the author email for this commit 409 # author_email (str) - the author email for this commit
395 # message_lines (tuple) - the lines of the commit message 410 # message_lines (tuple) - the lines of the commit message
411 # changed_files (frozenset) - all paths touched by this commit
396 class _GitilesCommitJson(namedtuple( 412 class _GitilesCommitJson(namedtuple(
397 '_GitilesCommitJson', 413 '_GitilesCommitJson',
398 'commit author_email commit_timestamp message_lines')): 414 'commit author_email commit_timestamp message_lines changed_files')):
399 @classmethod 415 @classmethod
400 def from_raw_json(cls, raw): 416 def from_raw_json(cls, raw):
417 mod_files = set()
418 for entry in raw['tree_diff']:
419 mod_files.add(entry['old_path'])
420 mod_files.add(entry['new_path'])
401 return cls( 421 return cls(
402 raw['commit'], 422 raw['commit'],
403 raw['author']['email'], 423 raw['author']['email'],
404 calendar.timegm(time.strptime(raw['committer']['time'])), 424 calendar.timegm(time.strptime(raw['committer']['time'])),
405 tuple(raw['message'].splitlines()), 425 tuple(raw['message'].splitlines()),
426 frozenset(mod_files),
406 ) 427 )
407 428
408 429
409 class GitilesBackend(Backend): 430 class GitilesBackend(Backend):
410 """GitilesBackend uses a repo served by Gitiles.""" 431 """GitilesBackend uses a repo served by Gitiles."""
411 432
412 # Prefix at the beginning of Gerrit/Gitiles JSON API responses. 433 # Prefix at the beginning of Gerrit/Gitiles JSON API responses.
413 _GERRIT_XSRF_HEADER = ')]}\'\n' 434 _GERRIT_XSRF_HEADER = ')]}\'\n'
414 435
415 @util.exponential_retry(condition=GitilesFetchError.transient) 436 @util.exponential_retry(condition=GitilesFetchError.transient)
416 def _fetch_gitiles(self, url_fmt, *args): 437 def _fetch_gitiles(self, url_fmt, *args):
417 """Fetches a remote URL path and returns the response object on success. 438 """Fetches a remote URL path and returns the response object on success.
418 439
419 Args: 440 Args:
420 url_fmt (str) - the url path fragment as a python %format string, like 441 url_fmt (str) - the url path fragment as a python %format string, like
421 '%s/foo/bar?something=value' 442 '%s/foo/bar?something=value'
422 *args (str) - the arguments to format url_fmt with. They will be URL 443 *args (str) - the arguments to format url_fmt with. They will be URL
423 escaped. 444 escaped.
424 445
425 Returns requests.Response. 446 Returns requests.Response.
426 """ 447 """
427 url = '%s/%s' % (self.repo_url, 448 url = '%s/%s' % (self.repo_url,
428 url_fmt % tuple(map(requests.utils.quote, args))) 449 url_fmt % tuple(map(requests.utils.quote, args)))
429 LOGGER.info('fetching %s' % url) 450 LOGGER.info('fetching %s' % url)
430 resp = requests.get(url) 451 resp = requests.get(url)
431 if resp.status_code != httplib.OK: 452 if resp.status_code != httplib.OK:
432 raise GitilesFetchError(resp.status_code, resp.text) 453 raise GitilesFetchError(resp.status_code, resp.text)
433 return resp 454 return resp
434 455
435 def _fetch_gitiles_json(self, url_fmt, *args): 456 def _fetch_gitiles_committish_json(self, url_fmt, *args):
436 """Fetches a remote URL path and expects a JSON object on success. 457 """Fetches a remote URL path and expects a JSON object on success.
437 458
459 This appends two GET params to url_fmt:
460 format=JSON - Does what you expect
461 name-status=1 - Ensures that commit objects returned have a 'tree_diff'
462 member which shows the diff for that commit.
463
438 Args: 464 Args:
439 url_fmt (str) - the url path fragment as a python %format string, like 465 url_fmt (str) - the url path fragment as a python %format string, like
440 '%s/foo/bar?something=value' 466 '%s/foo/bar?something=value'
441 *args (str) - the arguments to format url_fmt with. They will be URL 467 *args (str) - the arguments to format url_fmt with. They will be URL
442 escaped. 468 escaped.
443 469
444 Returns the decoded JSON object 470 Returns the decoded JSON object
445 """ 471 """
446 resp = self._fetch_gitiles(url_fmt, *args) 472 resp = self._fetch_gitiles(url_fmt+'?name-status=1&format=JSON', *args)
447 if not resp.text.startswith(self._GERRIT_XSRF_HEADER): 473 if not resp.text.startswith(self._GERRIT_XSRF_HEADER):
448 raise GitilesFetchError(resp.status_code, 'Missing XSRF prefix') 474 raise GitilesFetchError(resp.status_code, 'Missing XSRF prefix')
449 return json.loads(resp.text[len(self._GERRIT_XSRF_HEADER):]) 475 return json.loads(resp.text[len(self._GERRIT_XSRF_HEADER):])
450 476
451 # This caches entries from _fetch_commit_json. It's populated by 477 # This caches entries from _fetch_commit_json. It's populated by
452 # _fetch_commit_json as well as _updates_impl. 478 # _fetch_commit_json as well as _updates_impl.
453 # 479 #
454 # Mapping of: 480 # Mapping of:
455 # repo_url -> git_revision -> _GitilesCommitJson 481 # repo_url -> git_revision -> _GitilesCommitJson
456 # 482 #
457 # Only populated if _fetch_commit_json is passed a resolved commit. 483 # Only populated if _fetch_commit_json is passed a resolved commit.
458 _COMMIT_JSON_CACHE = {} 484 _COMMIT_JSON_CACHE = {}
459 485
460 def _fetch_commit_json(self, refspec): 486 def _fetch_commit_json(self, refspec):
461 """Returns _GitilesCommitJson for the refspec. 487 """Returns _GitilesCommitJson for the refspec.
462 488
463 If refspec is resolved then this value is cached. 489 If refspec is resolved then this value is cached.
464 """ 490 """
465 c = self._COMMIT_JSON_CACHE.setdefault(self.repo_url, {}) 491 c = self._COMMIT_JSON_CACHE.setdefault(self.repo_url, {})
466 if refspec in c: 492 if refspec in c:
467 return c[refspec] 493 return c[refspec]
468 494
469 raw = self._fetch_gitiles_json('+/%s?format=JSON', refspec) 495 raw = self._fetch_gitiles_committish_json('+/%s', refspec)
470 ret = _GitilesCommitJson.from_raw_json(raw) 496 ret = _GitilesCommitJson.from_raw_json(raw)
471 if self.is_resolved_revision(refspec): 497 if self.is_resolved_revision(refspec):
472 c[refspec] = ret 498 c[refspec] = ret
473 499
474 return ret 500 return ret
475 501
476 502
477 ### Backend implementations 503 ### Backend implementations
478 504
479 505
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
518 544
519 # TODO(iannucci): This implementation may be slow if we need to retieve 545 # TODO(iannucci): This implementation may be slow if we need to retieve
520 # multiple files/archives from the remote server. Should possibly consider 546 # multiple files/archives from the remote server. Should possibly consider
521 # using a thread pool here. 547 # using a thread pool here.
522 548
523 archive_response = self._fetch_gitiles( 549 archive_response = self._fetch_gitiles(
524 '+archive/%s/%s.tar.gz', revision, recipes_path_rel) 550 '+archive/%s/%s.tar.gz', revision, recipes_path_rel)
525 with tarfile.open(fileobj=StringIO(archive_response.content)) as tf: 551 with tarfile.open(fileobj=StringIO(archive_response.content)) as tf:
526 tf.extractall(recipes_path) 552 tf.extractall(recipes_path)
527 553
528 def _updates_impl(self, revision, other_revision, paths): 554 def _updates_impl(self, revision, other_revision):
529 self.assert_remote('_updates_impl') 555 self.assert_remote('_updates_impl')
530 556
531 # TODO(iannucci): implement paging 557 # TODO(iannucci): implement paging
532 558
533 # To include info about touched paths (tree_diff), pass name-status=1 below. 559 log_json = self._fetch_gitiles_committish_json(
534 log_json = self._fetch_gitiles_json( 560 '+log/%s..%s', revision, other_revision)
535 '+log/%s..%s?name-status=1&format=JSON', revision, other_revision)
536 561
537 c = self._COMMIT_JSON_CACHE.setdefault(self.repo_url, {}) 562 c = self._COMMIT_JSON_CACHE.setdefault(self.repo_url, {})
538 563
539 results = [] 564 results = []
540 for entry in log_json['log']: 565 for entry in log_json['log']:
541 commit = entry['commit'] 566 commit = entry['commit']
542 c[commit] = _GitilesCommitJson.from_raw_json(entry) 567 c[commit] = _GitilesCommitJson.from_raw_json(entry)
543 568 results.append(commit)
544 matched = False
545 for path in paths:
546 for diff_entry in entry['tree_diff']:
547 if (diff_entry['old_path'].startswith(path) or
548 diff_entry['new_path'].startswith(path)):
549 matched = True
550 break
551 if matched:
552 break
553 if matched or not paths:
554 results.append(commit)
555 569
556 results.reverse() 570 results.reverse()
557 return map(self.commit_metadata, results) 571 return map(self.commit_metadata, results)
558 572
559 def _resolve_refspec_impl(self, refspec): 573 def _resolve_refspec_impl(self, refspec):
560 if self.is_resolved_revision(refspec): 574 if self.is_resolved_revision(refspec):
561 return self.commit_metadata(refspec).commit 575 return self.commit_metadata(refspec).commit
562 return self._fetch_commit_json(refspec).commit 576 return self._fetch_commit_json(refspec).commit
563 577
564 def _commit_metadata_impl(self, revision): 578 def _commit_metadata_impl(self, revision):
565 self.assert_remote('_commit_metadata_impl') 579 self.assert_remote('_commit_metadata_impl')
566 rev_json = self._fetch_commit_json(revision) 580 rev_json = self._fetch_commit_json(revision)
567 581
568 recipes_cfg_text = self._fetch_gitiles( 582 recipes_cfg_text = self._fetch_gitiles(
569 '+/%s/infra/config/recipes.cfg?format=TEXT', revision 583 '+/%s/infra/config/recipes.cfg?format=TEXT', revision
570 ).text.decode('base64') 584 ).text.decode('base64')
571 spec = json_format.Parse( 585 spec = json_format.Parse(
572 recipes_cfg_text, package_pb2.Package(), ignore_unknown_fields=True) 586 recipes_cfg_text, package_pb2.Package(), ignore_unknown_fields=True)
573 587
574 return CommitMetadata( 588 return CommitMetadata(
575 revision, 589 revision,
576 rev_json.author_email, 590 rev_json.author_email,
577 rev_json.commit_timestamp, 591 rev_json.commit_timestamp,
578 rev_json.message_lines, 592 rev_json.message_lines,
579 spec) 593 spec,
594 has_interesting_changes(spec, rev_json.changed_files))
OLDNEW
« no previous file with comments | « recipe_engine/autoroll_impl/commit_list.py ('k') | recipe_engine/package.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698