Repository: Qiskit/qiskit-bot

Issue:
Changelog generation fails for versions >= 1.0.0
Since the release of qiskit 1.0.0 the changelog generation that runs as part of release process has been failing. Looking at the logs the `git log` command that's generated is not valid and getting confused by the concept of major versions (which to be fair for almost it's entire life weren't a thing). For example:

```
2024-05-16 20:02:49,238: 350 ERROR qiskit_bot.git [-] Failed to get git log
stdout:
b''
stderr:
b"fatal: ambiguous argument '1.1.0...0.0.0': unknown revision or path not in the working tree.\nUse '--' to separate paths from revisions, like this:\n'git <command> [<revision>...] -- [<file>...]'\n"
Traceback (most recent call last):
  File "/qiskit-bot/qiskit_bot/git.py", line 77, in get_git_log
    res = subprocess.run(['git', 'log', '--oneline', sha1],
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/subprocess.py", line 571, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['git', 'log', '--oneline', '1.1.0...0.0.0']' returned non-zero exit status 128.
Process Process-3:
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/multiprocessing/process.py", line 314, in _bootstrap
    self.run()
  File "/usr/local/lib/python3.11/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/qiskit-bot/qiskit_bot/release_process.py", line 259, in _finish_release__changelog_process
    create_github_release(repo, log_string, version_number,
  File "/qiskit-bot/qiskit_bot/release_process.py", line 213, in create_github_release
    changelog = _generate_changelog(repo, log_string, categories)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/qiskit-bot/qiskit_bot/release_process.py", line 142, in _generate_changelog
    git_log = git.get_git_log(repo, log_string).decode('utf8')
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'decode'
```

We should fix the release processing to correctly generate the git log from `1.1.0...1.0.0` as intended.

Discussion:

Patch:
diff --git a/qiskit_bot/release_process.py b/qiskit_bot/release_process.py
index 06afee7..3d17c22 100644
--- a/qiskit_bot/release_process.py
+++ b/qiskit_bot/release_process.py
@@ -226,7 +226,7 @@ def _get_log_string(version_obj, version_number, repo):
     # If a patch release log between 0.A.X..0.A.X-1
     elif version_obj.micro > 0:
         old_version = (
-            f"{version_obj.epoch}.{version_obj.minor}."
+            f"{version_obj.major}.{version_obj.minor}."
             f"{version_obj.micro - 1}"
         )
     # If a major version log between X.0.0..x-1.y.z
@@ -240,9 +240,9 @@ def _get_log_string(version_obj, version_number, repo):
             if tag_version.major == previous_major:
                 old_version = tag
                 break
-    # If a minor release log between 0.X.0..0.X-1.0
+    # If a minor release log between Y.X.0..Y.X-1.0
     else:
-        old_version = f"{version_obj.epoch}.{version_obj.minor - 1}.0"
+        old_version = f"{version_obj.major}.{version_obj.minor - 1}.0"
     return f"{version_number}...{old_version}"
 
 


Test patch:
diff --git a/tests/test_release_process.py b/tests/test_release_process.py
index fd7072c..e822677 100644
--- a/tests/test_release_process.py
+++ b/tests/test_release_process.py
@@ -839,30 +839,166 @@ qiskit-terra==0.16.0
 
     def test_get_log_string(self):
         version_obj = parse("0.10.2")
-        self.assertEqual('0.10.2...0.10.1',
-                         release_process._get_log_string(
-                             version_obj,
-                             "0.10.2",
-                             unittest.mock.MagicMock()
-                         ))
+        self.assertEqual(
+            "0.10.2...0.10.1",
+            release_process._get_log_string(
+                version_obj, "0.10.2", unittest.mock.MagicMock()
+            ),
+        )
         version_obj = parse("0.3.0")
-        self.assertEqual('0.3.0...0.2.0',
-                         release_process._get_log_string(
-                             version_obj,
-                             "0.3.0",
-                             unittest.mock.MagicMock()))
+        self.assertEqual(
+            "0.3.0...0.2.0",
+            release_process._get_log_string(
+                version_obj, "0.3.0", unittest.mock.MagicMock()
+            ),
+        )
         version_obj = parse("0.3.25")
-        self.assertEqual('0.3.25...0.3.24',
-                         release_process._get_log_string(
-                             version_obj,
-                             "0.3.25",
-                             unittest.mock.MagicMock()))
+        self.assertEqual(
+            "0.3.25...0.3.24",
+            release_process._get_log_string(
+                version_obj, "0.3.25", unittest.mock.MagicMock()
+            ),
+        )
         version_obj = parse("0.25.0")
-        self.assertEqual('0.25.0...0.24.0',
-                         release_process._get_log_string(
-                             version_obj,
-                             "0.25.0",
-                             unittest.mock.MagicMock()))
+        self.assertEqual(
+            "0.25.0...0.24.0",
+            release_process._get_log_string(
+                version_obj, "0.25.0", unittest.mock.MagicMock()
+            ),
+        )
+
+    @unittest.mock.patch.object(release_process, 'git')
+    def test_get_log_string_major_1_0_0(self, git_mock):
+        # Tests for >= 1.x.x
+        self.useFixture(
+            fake_meta.FakeMetaRepo(self.temp_dir, "1.0.0",
+                                   terra_version="1.0.0")
+        )
+        # Mock tags for 1.0.0-0.45.0
+        git_mock.get_tags.return_value = """1.0.0
+1.0.0rc1
+0.46.0
+0.45.3
+0.45.2
+1.0.0b1
+0.45.1
+0.45.0
+"""
+        # Prepare mock repo
+        mock_repo = unittest.mock.MagicMock()
+        mock_repo.name = "qiskit"
+        mock_repo.repo_name = "Qiskit/qiskit"
+        mock_repo.repo_config = {"optional_package": False}
+        mock_repo.local_path = self.temp_dir.path
+
+        # Test for major release 1.0.0
+        version_obj = parse("1.0.0")
+        self.assertEqual(
+            "1.0.0...0.46.0",
+            release_process._get_log_string(version_obj, "1.0.0", mock_repo),
+        )
+
+    @unittest.mock.patch.object(release_process, 'git')
+    def test_get_log_string_post_1_x_x(self, git_mock):
+        # Tests for >= 1.x.x
+        self.useFixture(
+            fake_meta.FakeMetaRepo(self.temp_dir, "1.1.0",
+                                   terra_version="1.1.1")
+        )
+        # Mock tags for 1.1.1-0.45.0
+        git_mock.get_tags.return_value = """0.46.2
+1.1.1
+0.46.2
+1.1.1
+1.1.0
+1.1.0rc1
+0.46.1
+1.0.2
+1.0.1
+1.0.0
+1.0.0rc1
+0.46.0
+0.45.3
+0.45.2
+1.0.0b1
+0.45.1
+0.45.0
+"""
+        mock_repo = unittest.mock.MagicMock()
+        mock_repo.name = "qiskit"
+        mock_repo.repo_name = "Qiskit/qiskit"
+        mock_repo.repo_config = {"optional_package": False}
+        mock_repo.local_path = self.temp_dir.path
+
+        # Test minor release 1.1.0
+        version_obj = parse("1.1.0")
+        self.assertEqual(
+            "1.1.0...1.0.0",
+            release_process._get_log_string(version_obj, "1.1.0", mock_repo),
+        )
+
+        # Test bugfix release 1.1.1
+        version_obj = parse("1.1.1")
+        self.assertEqual(
+            "1.1.1...1.1.0",
+            release_process._get_log_string(version_obj, "1.1.1", mock_repo),
+        )
+
+    @unittest.mock.patch.object(release_process, 'git')
+    def test_get_log_string_post_2_x_x(self, git_mock):
+        # Tests for >= 2.x.x
+        self.useFixture(
+            fake_meta.FakeMetaRepo(self.temp_dir, "1.1.0",
+                                   terra_version="1.1.1")
+        )
+        # Mock tags for 2.0.0rc1-1.3.0rc1
+        git_mock.get_tags.return_value = """2.0.0
+2.0.0.rc1
+1.4.0
+1.4.0rc1
+1.3.2
+1.3.1
+1.3.0
+1.2.2
+1.3.0rc1
+"""
+        # Prepare mock repository
+        mock_repo = unittest.mock.MagicMock()
+        mock_repo.name = "qiskit"
+        mock_repo.repo_name = "Qiskit/qiskit"
+        mock_repo.repo_config = {"optional_package": False}
+        mock_repo.local_path = self.temp_dir.path
+
+        # Test for release candidate 1.4.0rc1
+        version_obj = parse("1.4.0rc1")
+        self.assertEqual(
+            "1.4.0rc1...1.3.0",
+            release_process._get_log_string(version_obj, "1.4.0rc1",
+                                            mock_repo),
+        )
+
+        # Test for minor release 1.4.0
+        version_obj = parse("1.4.0")
+        self.assertEqual(
+            "1.4.0...1.3.0",
+            release_process._get_log_string(version_obj, "1.4.0", mock_repo),
+        )
+
+        # Test for major release candidate 2.0.0rc1
+        version_obj = parse("2.0.0rc1")
+        self.assertEqual(
+            "2.0.0rc1...1.4.0",
+            release_process._get_log_string(version_obj, "2.0.0rc1",
+                                            mock_repo),
+        )
+
+        # Test for major release 2.0.0
+        version_obj = parse("2.0.0")
+        self.assertEqual(
+            "2.0.0...1.4.0",
+            release_process._get_log_string(version_obj, "2.0.0",
+                                            mock_repo),
+        )
 
     def test_get_log_string_prerelease(self):
         version_obj = parse("0.25.0rc1")