-
Notifications
You must be signed in to change notification settings - Fork 2
/
find_closest_git_version.py
120 lines (99 loc) · 3.97 KB
/
find_closest_git_version.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# Copyright (c) 2018 The University of Manchester
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" Get the "closest" git version to a given version on a given repository.
If the version is semantic formatted as <major>.<minor>.<patch> e.g. 4.0.1,
this will print a version with the same major version, and with a minor version
either the same or greater than the given minor version. If a version with
the same patch version exists, this will be printed, otherwise a greater
patch number if possible, but if not, the greatest patch number is used.
If the same major version does not exist, or only minor versions less than
the minor version exist, master is printed.
If a non-semantic version is given, if a branch or tag exists with the version,
this is printed, otherwise master is printed.
If a third argument is given, this is taken as a version prefix to be used;
any semantic version will be assumed to have this prefix.
"""
from collections import defaultdict
import subprocess
import re
import sys
import os
def print_version(prefix, major, minor, patch):
""" Print the version
"""
print("{}{}.{}.{}".format(prefix, major, minor, patch))
# Get the arguments
repository = sys.argv[1]
version = sys.argv[2]
prefix = ""
if len(sys.argv) > 3:
prefix = sys.argv[3]
# Check if the version is a semantic version
version_match = re.match(r"{}(\d+)\.(\d+)\.(\d+)".format(prefix), version)
if not version_match:
# If not a semantic version, check if the branch or tag exists
FNULL = open(os.devnull, 'w')
result = subprocess.call(
["git", "ls-remote", "--exit-code", repository, version],
stdout=FNULL, stderr=subprocess.STDOUT)
if result == 0:
print(version)
sys.exit(0)
# If it doesn't exist, return master
print("master")
sys.exit(0)
# If a semantic version, split in to parts
v_major = int(version_match.group(1))
v_minor = int(version_match.group(2))
v_patch = int(version_match.group(3))
# Run git ls-remote to get the list of branches and tags
git_process = subprocess.check_output(["git", "ls-remote", repository])
git_output = git_process.decode("utf-8")
# Extract versions that are semantic
pattern = re.compile(
r"^[^\s]+\s+refs/(heads|tags)/{}(\d+)\.(\d+)\.(\d+)$".format(prefix))
versions = defaultdict(set)
for line in git_output.split("\n"):
matcher = pattern.match(line)
if matcher:
major = int(matcher.group(2))
minor = int(matcher.group(3))
patch = int(matcher.group(4))
versions[major].add((minor, patch))
# If the major version doesn't exist, assume master
if v_major not in versions:
print("master")
sys.exit(0)
# If the version exists exactly, return it
matching = versions[v_major]
if (v_minor, v_patch) in matching:
print_version(prefix, v_major, v_minor, v_patch)
sys.exit(0)
# Look through the versions that are minor and patch of the major
incomplete_match = None
for (minor, patch) in sorted(matching):
# If the version is the same or higher, take it
if (minor == v_minor and patch >= v_patch) or (minor > v_minor):
print_version(prefix, v_major, minor, patch)
sys.exit(0)
# If the version is the same, but the patch is not higher, store for later
elif minor == v_minor:
incomplete_match = (minor, patch)
# If the only match was a minor without a patch, use it
if incomplete_match is not None:
minor, patch = incomplete_match
print_version(prefix, v_major, minor, patch)
sys.exit(0)
# If no match, use master
print("master")