Repository: conan-io/conan
Pull request #11830 (2022-08-10 10:38:40)

Port #11823 to 1.50.2
Changelog: Bugfix: The `tool.scm.Version` model has been ported from 2.X to keep the same behavior in Conan 1.X
Docs: omit


Patch:
diff --git a/conan/tools/scm/__init__.py b/conan/tools/scm/__init__.py
index 9d4097bc7..59846cc18 100644
--- a/conan/tools/scm/__init__.py
+++ b/conan/tools/scm/__init__.py
@@ -1,2 +1,200 @@
 from conan.tools.scm.git import Git
-from conans.model.version import Version
+# develop2 uncomment the next line
+# from conans.model.version import Version
+
+
+# ######## DEVELOP2: Delete all the contents from here, this is copy-paste from the Version model
+# ######## at conans.model.version (for an easier migration)
+from functools import total_ordering
+from conans.errors import ConanException
+
+
+@total_ordering
+class _VersionItem:
+    """ a single "digit" in a version, like X.Y.Z all X and Y and Z are VersionItems
+    They can be int or strings
+    """
+    def __init__(self, item):
+        try:
+            self._v = int(item)
+        except ValueError:
+            self._v = item
+
+    @property
+    def value(self):
+        return self._v
+
+    def __str__(self):
+        return str(self._v)
+
+    def __add__(self, other):
+        # necessary for the "bump()" functionality. Other aritmetic operations are missing
+        return self._v + other
+
+    def __eq__(self, other):
+        if not isinstance(other, _VersionItem):
+            other = _VersionItem(other)
+        return self._v == other._v
+
+    def __hash__(self):
+        return hash(self._v)
+
+    def __lt__(self, other):
+        """
+        @type other: _VersionItem
+        """
+        if not isinstance(other, _VersionItem):
+            other = _VersionItem(other)
+        try:
+            return self._v < other._v
+        except TypeError:
+            return str(self._v) < str(other._v)
+
+
+@total_ordering
+class Version:
+    """
+    This is NOT an implementation of semver, as users may use any pattern in their versions.
+    It is just a helper to parse "." or "-" and compare taking into account integers when possible
+    """
+    def __init__(self, value):
+        value = str(value)
+        self._value = value
+
+        items = value.rsplit("+", 1)  # split for build
+        if len(items) == 2:
+            value, build = items
+            self._build = Version(build)  # This is a nested version by itself
+        else:
+            value = items[0]
+            self._build = None
+
+        items = value.rsplit("-", 1)  # split for pre-release
+        if len(items) == 2:
+            value, pre = items
+            self._pre = Version(pre)  # This is a nested version by itself
+        else:
+            value = items[0]
+            self._pre = None
+        items = value.split(".")
+        items = [_VersionItem(item) for item in items]
+        self._items = tuple(items)
+        while items and items[-1].value == 0:
+            del items[-1]
+        self._nonzero_items = tuple(items)
+
+    def bump(self, index):
+        """
+           Increments by 1 the version field at the specified index, setting to 0 the fields
+            on the right.
+           2.5 => bump(1) => 2.6
+           1.5.7 => bump(0) => 2.0.0
+        """
+        # this method is used to compute version ranges from tilde ~1.2 and caret ^1.2.1 ranges
+        # TODO: at this moment it only works for digits, cannot increment pre-release or builds
+        # better not make it public yet, keep it internal
+        items = list(self._items[:index])
+        try:
+            items.append(self._items[index]+1)
+        except TypeError:
+            raise ConanException(f"Cannot bump '{self._value} version index {index}, not an int")
+        items.extend([0] * (len(items) - index - 1))
+        v = ".".join(str(i) for i in items)
+        # prerelease and build are dropped while bumping digits
+        result = Version(v)
+        return result
+
+    def upper_bound(self, index):
+        items = list(self._items[:index])
+        try:
+            items.append(self._items[index] + 1)
+        except TypeError:
+            raise ConanException(f"Cannot bump '{self._value} version index {index}, not an int")
+        items.extend([0] * (len(items) - index - 1))
+        v = ".".join(str(i) for i in items)
+        v += "-"  # Exclude prereleases
+        result = Version(v)
+        return result
+
+    @property
+    def pre(self):
+        return self._pre
+
+    @property
+    def build(self):
+        return self._build
+
+    @property
+    def main(self):
+        return self._items
+
+    @property
+    def major(self):
+        try:
+            return self.main[0]
+        except IndexError:
+            return None
+
+    @property
+    def minor(self):
+        try:
+            return self.main[1]
+        except IndexError:
+            return None
+
+    @property
+    def patch(self):
+        try:
+            return self.main[2]
+        except IndexError:
+            return None
+
+    @property
+    def micro(self):
+        try:
+            return self.main[3]
+        except IndexError:
+            return None
+
+    def __str__(self):
+        return self._value
+
+    def __repr__(self):
+        return self._value
+
+    def __eq__(self, other):
+        if other is None:
+            return False
+        if isinstance(other, str):
+            other = Version(other)
+
+        return (self._nonzero_items, self._pre, self._build) ==\
+               (other._nonzero_items, other._pre, other._build)
+
+    def __hash__(self):
+        return hash((self._nonzero_items, self._pre, self._build))
+
+    def __lt__(self, other):
+        if other is None:
+            return False
+        if not isinstance(other, Version):
+            other = Version(other)
+
+        if self._pre:
+            if other._pre:  # both are pre-releases
+                return (self._nonzero_items, self._pre, self._build) < \
+                       (other._nonzero_items, other._pre, other._build)
+            else:  # Left hand is pre-release, right side is regular
+                if self._nonzero_items == other._nonzero_items:  # Problem only happens if both equal
+                    return True
+                else:
+                    return self._nonzero_items < other._nonzero_items
+        else:
+            if other._pre:  # Left hand is regular, right side is pre-release
+                if self._nonzero_items == other._nonzero_items:  # Problem only happens if both equal
+                    return False
+                else:
+                    return self._nonzero_items < other._nonzero_items
+            else:  # None of them is pre-release
+                return (self._nonzero_items, self._build) < (other._nonzero_items, other._build)
+
diff --git a/conans/__init__.py b/conans/__init__.py
index cc354d760..4eca40110 100644
--- a/conans/__init__.py
+++ b/conans/__init__.py
@@ -20,4 +20,4 @@ OAUTH_TOKEN = "oauth_token"
 SERVER_CAPABILITIES = [COMPLEX_SEARCH_CAPABILITY, REVISIONS]  # Server is always with revisions
 DEFAULT_REVISION_V1 = "0"
 
-__version__ = '1.50.1'
+__version__ = '1.50.2'
diff --git a/conans/client/migrations_settings.py b/conans/client/migrations_settings.py
index b66cb0cdc..b23b0fcf6 100644
--- a/conans/client/migrations_settings.py
+++ b/conans/client/migrations_settings.py
@@ -3714,3 +3714,4 @@ cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20, 23, gnu23]
 
 settings_1_50_0 = settings_1_49_0
 settings_1_50_1 = settings_1_50_0
+settings_1_50_2 = settings_1_50_1


Test patch:
diff --git a/conans/test/unittests/tools/scm/test_version.py b/conans/test/unittests/tools/scm/test_version.py
index bdb83344a..7c52bb49e 100644
--- a/conans/test/unittests/tools/scm/test_version.py
+++ b/conans/test/unittests/tools/scm/test_version.py
@@ -16,8 +16,17 @@ def test_version():
             def configure(self):
                 v = Version(self.settings.compiler.version)
                 assert v > "10"
+
+                v = Version("1.2.3")
+                self.output.warning(f"The major of 1.2.3 is {v.major}")
+                self.output.warning(f"The minor of 1.2.3 is {v.minor}")
+                self.output.warning(f"The patch of 1.2.3 is {v.patch}")
         """)
     c.save({"conanfile.py": conanfile})
     settings = "-s compiler=gcc -s compiler.libcxx=libstdc++11"
     c.run("create . {} -s compiler.version=11".format(settings))
+    assert "The major of 1.2.3 is 1" in c.out
+    assert "The minor of 1.2.3 is 2" in c.out
+    assert "The patch of 1.2.3 is 3" in c.out
+
     c.run("create . {} -s compiler.version=9".format(settings), assert_error=True)