Index: recipe_engine/fetch.py |
diff --git a/recipe_engine/fetch.py b/recipe_engine/fetch.py |
index 8f417bf34906bf9da46a318720484a0ff8546544..4e5313227ed106226ad95ca2107fc2bab7187706 100644 |
--- a/recipe_engine/fetch.py |
+++ b/recipe_engine/fetch.py |
@@ -31,6 +31,14 @@ from google.protobuf import json_format |
LOGGER = logging.getLogger(__name__) |
+def has_interesting_changes(spec, changed_files): |
+ # TODO(iannucci): analyze bundle_extra_paths.txt too. |
+ return ( |
+ 'infra/config/recipes.cfg' in changed_files or |
+ any(f.startswith(spec.recipes_path) for f in changed_files) |
+ ) |
+ |
+ |
class FetchError(Exception): |
pass |
@@ -48,9 +56,12 @@ class UnresolvedRefspec(Exception): |
# commit_timestamp (int): the unix commit timestamp for this commit |
# message_lines (tuple(str)): the message of this commit |
# spec (package_pb2.Package): the parsed infra/config/recipes.cfg file or None. |
+# roll_candidate (bool): if this commit contains changes which are known to |
+# affect the behavior of the recipes (i.e. modifications within recipe_path |
+# and/or modifications to recipes.cfg) |
CommitMetadata = namedtuple( |
'_CommitMetadata', |
- 'revision author_email commit_timestamp message_lines spec') |
+ 'revision author_email commit_timestamp message_lines spec roll_candidate') |
class Backend(object): |
@@ -114,11 +125,7 @@ class Backend(object): |
The refspec will be resolved if it's not absolute. |
- Returns { |
- 'author': '<author name>', |
- 'message': '<commit message>', |
- 'spec': package_pb2.Package or None, # the parsed recipes.cfg file. |
- } |
+ Returns (CommitMetadata). |
""" |
revision = self.resolve_refspec(refspec) |
cache = self._GIT_METADATA_CACHE.setdefault(self.repo_url, {}) |
@@ -140,18 +147,16 @@ class Backend(object): |
return refspec |
return self._resolve_refspec_impl(refspec) |
- def updates(self, revision, other_revision, paths): |
+ def updates(self, revision, other_revision): |
"""Returns a list of revisions |revision| through |other_revision| |
(inclusive). |
- If |paths| is a non-empty list, the history is scoped just to these paths. |
- |
Returns list(CommitMetadata) - The commit metadata in the range |
(revision,other_revision]. |
""" |
self.assert_resolved(revision) |
self.assert_resolved(other_revision) |
- return self._updates_impl(revision, other_revision, paths) |
+ return self._updates_impl(revision, other_revision) |
### direct overrides. These are public methods which must be overridden. |
@@ -185,12 +190,10 @@ class Backend(object): |
### private overrides. Override these in the implementations, but don't call |
### externally. |
- def _updates_impl(self, revision, other_revision, paths): |
+ def _updates_impl(self, revision, other_revision): |
"""Returns a list of revisions |revision| through |other_revision|. This |
includes |revision| and |other_revision|. |
- If |paths| is a non-empty list, the history is scoped just to these paths. |
- |
Args: |
revision (str) - the first git commit |
other_revision (str) - the second git commit |
@@ -327,15 +330,13 @@ class GitBackend(Backend): |
except GitError: |
self._git('reset', '-q', '--hard', revision) |
- def _updates_impl(self, revision, other_revision, paths): |
+ def _updates_impl(self, revision, other_revision): |
args = [ |
'rev-list', |
'--reverse', |
'--topo-order', |
'%s..%s' % (revision, other_revision), |
] |
- if paths: |
- args.extend(['--'] + paths) |
return [ |
self.commit_metadata(rev) |
for rev in self._git(*args).strip().split('\n') |
@@ -367,9 +368,14 @@ class GitBackend(Backend): |
except GitError: |
spec = None |
+ # check diff to see if it touches anything interesting. |
+ changed_files = set(self._git( |
+ 'diff-tree', '-r', '--no-commit-id', '--name-only', '%s^!' % revision) |
+ .splitlines()) |
+ |
return CommitMetadata(revision, meta[0], |
int(meta[1]), tuple(meta[2:]), |
- spec) |
+ spec, has_interesting_changes(spec, changed_files)) |
class GitilesFetchError(FetchError): |
"""An HTTP error that occurred during Gitiles fetching.""" |
@@ -393,16 +399,22 @@ class GitilesFetchError(FetchError): |
# commit (str) - the git commit hash |
# author_email (str) - the author email for this commit |
# message_lines (tuple) - the lines of the commit message |
+# changed_files (frozenset) - all paths touched by this commit |
class _GitilesCommitJson(namedtuple( |
'_GitilesCommitJson', |
- 'commit author_email commit_timestamp message_lines')): |
+ 'commit author_email commit_timestamp message_lines changed_files')): |
@classmethod |
def from_raw_json(cls, raw): |
+ mod_files = set() |
+ for entry in raw['tree_diff']: |
+ mod_files.add(entry['old_path']) |
+ mod_files.add(entry['new_path']) |
return cls( |
raw['commit'], |
raw['author']['email'], |
calendar.timegm(time.strptime(raw['committer']['time'])), |
tuple(raw['message'].splitlines()), |
+ frozenset(mod_files), |
) |
@@ -432,9 +444,14 @@ class GitilesBackend(Backend): |
raise GitilesFetchError(resp.status_code, resp.text) |
return resp |
- def _fetch_gitiles_json(self, url_fmt, *args): |
+ def _fetch_gitiles_committish_json(self, url_fmt, *args): |
"""Fetches a remote URL path and expects a JSON object on success. |
+ This appends two GET params to url_fmt: |
+ format=JSON - Does what you expect |
+ name-status=1 - Ensures that commit objects returned have a 'tree_diff' |
+ member which shows the diff for that commit. |
+ |
Args: |
url_fmt (str) - the url path fragment as a python %format string, like |
'%s/foo/bar?something=value' |
@@ -443,7 +460,7 @@ class GitilesBackend(Backend): |
Returns the decoded JSON object |
""" |
- resp = self._fetch_gitiles(url_fmt, *args) |
+ resp = self._fetch_gitiles(url_fmt+'?name-status=1&format=JSON', *args) |
if not resp.text.startswith(self._GERRIT_XSRF_HEADER): |
raise GitilesFetchError(resp.status_code, 'Missing XSRF prefix') |
return json.loads(resp.text[len(self._GERRIT_XSRF_HEADER):]) |
@@ -466,7 +483,7 @@ class GitilesBackend(Backend): |
if refspec in c: |
return c[refspec] |
- raw = self._fetch_gitiles_json('+/%s?format=JSON', refspec) |
+ raw = self._fetch_gitiles_committish_json('+/%s', refspec) |
ret = _GitilesCommitJson.from_raw_json(raw) |
if self.is_resolved_revision(refspec): |
c[refspec] = ret |
@@ -525,14 +542,13 @@ class GitilesBackend(Backend): |
with tarfile.open(fileobj=StringIO(archive_response.content)) as tf: |
tf.extractall(recipes_path) |
- def _updates_impl(self, revision, other_revision, paths): |
+ def _updates_impl(self, revision, other_revision): |
self.assert_remote('_updates_impl') |
# TODO(iannucci): implement paging |
- # To include info about touched paths (tree_diff), pass name-status=1 below. |
- log_json = self._fetch_gitiles_json( |
- '+log/%s..%s?name-status=1&format=JSON', revision, other_revision) |
+ log_json = self._fetch_gitiles_committish_json( |
+ '+log/%s..%s', revision, other_revision) |
c = self._COMMIT_JSON_CACHE.setdefault(self.repo_url, {}) |
@@ -540,18 +556,7 @@ class GitilesBackend(Backend): |
for entry in log_json['log']: |
commit = entry['commit'] |
c[commit] = _GitilesCommitJson.from_raw_json(entry) |
- |
- matched = False |
- for path in paths: |
- for diff_entry in entry['tree_diff']: |
- if (diff_entry['old_path'].startswith(path) or |
- diff_entry['new_path'].startswith(path)): |
- matched = True |
- break |
- if matched: |
- break |
- if matched or not paths: |
- results.append(commit) |
+ results.append(commit) |
results.reverse() |
return map(self.commit_metadata, results) |
@@ -576,4 +581,5 @@ class GitilesBackend(Backend): |
rev_json.author_email, |
rev_json.commit_timestamp, |
rev_json.message_lines, |
- spec) |
+ spec, |
+ has_interesting_changes(spec, rev_json.changed_files)) |