You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildstream.apache.org by ro...@apache.org on 2020/12/29 13:49:48 UTC

[buildstream] 10/13: _gitsourcebase.py: Add 'track-latest-tag' option

This is an automated email from the ASF dual-hosted git repository.

root pushed a commit to branch tmewett/merge-git-tag
in repository https://gitbox.apache.org/repos/asf/buildstream.git

commit 6e34512fa9fb81ba15654c74af4eb240201a14b6
Author: Tom Mewett <to...@codethink.co.uk>
AuthorDate: Tue Jan 14 10:33:05 2020 +0000

    _gitsourcebase.py: Add 'track-latest-tag' option
    
    This modifies the behaviour of tracking the source to choosing the latest
    tagged commit instead of the branch tip.
---
 src/buildstream/_gitsourcebase.py | 98 +++++++++++++++++++++++++++++++++++----
 1 file changed, 90 insertions(+), 8 deletions(-)

diff --git a/src/buildstream/_gitsourcebase.py b/src/buildstream/_gitsourcebase.py
index c73e86d..365a459 100644
--- a/src/buildstream/_gitsourcebase.py
+++ b/src/buildstream/_gitsourcebase.py
@@ -31,7 +31,7 @@ from configparser import RawConfigParser
 
 from .source import Source, SourceError, SourceFetcher
 from .types import Consistency, CoreWarnings
-from .node import ScalarNode, SequenceNode
+from .node import MappingNode, ScalarNode, SequenceNode
 from . import utils
 from .types import FastEnum
 from .utils import move_atomic, DirectoryExistsError
@@ -39,6 +39,7 @@ from .utils import move_atomic, DirectoryExistsError
 GIT_MODULES = ".gitmodules"
 
 # Warnings
+WARN_TAG_NOT_FOUND = "tag-not-found"
 WARN_INCONSISTENT_SUBMODULE = "inconsistent-submodule"
 WARN_UNLISTED_SUBMODULE = "unlisted-submodule"
 WARN_INVALID_SUBMODULE = "invalid-submodule"
@@ -64,6 +65,10 @@ def _has_matching_ref(refs, tag, commit):
     return any(ref_commit == commit and ref_name in names for ref_commit, ref_name in refs)
 
 
+def _undescribe(rev):
+    return rev.split("-g")[-1]
+
+
 # This class represents a single Git repository. The Git source needs to account for
 # submodules, but we don't want to cache them all under the umbrella of the
 # superproject - so we use this class which caches them independently, according
@@ -223,6 +228,35 @@ class _GitMirror(SourceFetcher):
         )
         return output.strip()
 
+    # latest_tagged():
+    #
+    # Args:
+    #     branch (str)
+    #     match (list): Optional list of glob pattern strings
+    #     exclude (list): Optional list of glob pattern strings
+    #
+    # For the behaviour of match and exclude, see the corresponding flags of `git describe`.
+    #
+    # Returns:
+    #     (str or None): A Git revision of the latest tagged commit on the given branch.
+    #                    Only tags with names meeting the criteria of `match` and `exclude` are considered.
+    #                    If no matching tags are found, None is returned.
+    #
+    def latest_tagged(self, branch, match=None, exclude=None):
+        pattern_args = []
+        if match:
+            for pat in match:
+                pattern_args += ["--match", pat]
+        if exclude:
+            for pat in exclude:
+                pattern_args += ["--exclude", pat]
+
+        exit_code, output = self.source.check_output(
+            [self.source.host_git, "describe", "--tags", "--abbrev=0", *pattern_args, branch], cwd=self.mirror
+        )
+
+        return output.strip() + "^{commit}" if exit_code == 0 else None
+
     # date_of_commit():
     #
     # Args:
@@ -518,7 +552,17 @@ class _GitSourceBase(Source):
     def configure(self, node):
         ref = node.get_str("ref", None)
 
-        config_keys = ["url", "track", "ref", "submodules", "checkout-submodules", "ref-format", "track-tags", "tags"]
+        config_keys = [
+            "url",
+            "track",
+            "ref",
+            "submodules",
+            "checkout-submodules",
+            "ref-format",
+            "track-latest-tag",
+            "track-tags",
+            "tags",
+        ]
         node.validate_keys(config_keys + Source.COMMON_CONFIG_KEYS)
 
         tags_node = node.get_sequence("tags", [])
@@ -528,6 +572,20 @@ class _GitSourceBase(Source):
         tags = self._load_tags(node)
         self.track_tags = node.get_bool("track-tags", default=False)
 
+        self.match_tags = []
+        self.exclude_tags = []
+        # Allow 'track-latest-tag' to be either a bool or a mapping configuring its sub-options
+        latest_tag = node.get_node("track-latest-tag", [ScalarNode, MappingNode], allow_none=True)
+        if isinstance(latest_tag, MappingNode):
+            latest_tag.validate_keys(["match", "exclude"])
+            self.match_tags += latest_tag.get_str_list("match", [])
+            self.exclude_tags += latest_tag.get_str_list("exclude", [])
+            self.track_latest_tag = True
+        elif latest_tag is not None:
+            self.track_latest_tag = latest_tag.as_bool()
+        else:
+            self.track_latest_tag = False
+
         self.original_url = node.get_str("url")
         self.mirror = self.BST_MIRROR_CLASS(self, "", self.original_url, ref, tags=tags, primary=True)
 
@@ -579,7 +637,7 @@ class _GitSourceBase(Source):
         if ref is not None:
             # If the ref contains "-g" (is in git-describe format),
             # only choose the part after, which is the commit ID
-            ref = ref.split("-g")[-1]
+            ref = _undescribe(ref)
 
         # Here we want to encode the local name of the repository and
         # the ref, if the user changes the alias to fetch the same sources
@@ -656,13 +714,37 @@ class _GitSourceBase(Source):
         with self.timed_activity("Tracking {} from {}".format(self.tracking, resolved_url), silent_nested=True):
             self.mirror._fetch(resolved_url)
 
-            refs = [self.mirror.latest_commit(branch) for branch in self.tracking]
-            ref = max(refs, key=self.mirror.date_of_commit)
-            tags = self.mirror.reachable_tags(ref) if self.track_tags else []
+            if self.track_latest_tag:
+                revs = []
+                without_tags = []
+                for branch in self.tracking:
+                    rev = self.mirror.latest_tagged(branch, match=self.match_tags, exclude=self.exclude_tags)
+                    if rev is None:
+                        without_tags.append(branch)
+                    else:
+                        revs.append(rev)
+
+                if without_tags:
+                    self.warn(
+                        "{}: Cannot find tags on all track targets".format(self),
+                        warning_token=WARN_TAG_NOT_FOUND,
+                        detail="No matching tags were found on the following track targets:\n\n"
+                        + "\n".join(without_tags),
+                    )
 
-            if self.ref_format == _RefFormat.GIT_DESCRIBE:
-                ref = self.mirror.describe(ref)
+                if not revs:
+                    raise SourceError("{}: Failed to find any revisions to track".format(self))
 
+            else:
+                revs = [self.mirror.latest_commit(branch) for branch in self.tracking]
+
+            ref = max(revs, key=self.mirror.date_of_commit)
+
+            ref = self.mirror.describe(ref)  # normalise our potential mixture of rev formats
+            if self.ref_format == _RefFormat.SHA1:
+                ref = _undescribe(ref)
+
+            tags = self.mirror.reachable_tags(ref) if self.track_tags else []
             return ref, tags
 
     def init_workspace(self, directory):