1#!/usr/bin/env python
2
3# internal use only
4# called by CI jobs when it uses a project related to IDF
5
6import argparse
7import json
8import os
9import re
10import subprocess
11
12IDF_GIT_DESCRIBE_PATTERN = re.compile(r'^v(\d)\.(\d)')
13RETRY_COUNT = 3
14
15
16def get_customized_project_revision(proj_name):
17    """
18    get customized project revision defined in bot message
19    """
20    revision = ''
21    customized_project_revisions = os.getenv('BOT_CUSTOMIZED_REVISION')
22    if customized_project_revisions:
23        customized_project_revisions = json.loads(customized_project_revisions)
24    try:
25        revision = customized_project_revisions[proj_name.lower()]
26    except (KeyError, TypeError):
27        pass
28    return revision
29
30
31def target_branch_candidates(proj_name):
32    """
33    :return: a list of target branch candidates, from highest priority to lowest priority.
34    """
35    candidates = [
36        # branch name (or tag name) of current IDF
37        os.getenv('CI_COMMIT_REF_NAME'),
38        # CI_MERGE_REQUEST_TARGET_BRANCH_NAME
39        os.getenv('CI_MERGE_REQUEST_TARGET_BRANCH_NAME'),
40    ]
41    customized_candidate = get_customized_project_revision(proj_name)
42    if customized_candidate:
43        # highest priority, insert to head of list
44        candidates.insert(0, customized_candidate)
45
46    # branch name read from IDF
47    try:
48        git_describe = subprocess.check_output(['git', 'describe', 'HEAD'])
49        match = IDF_GIT_DESCRIBE_PATTERN.search(git_describe.decode())
50        if match:
51            major_revision = match.group(1)
52            minor_revision = match.group(2)
53            # release branch
54            candidates.append('release/v{}.{}'.format(major_revision, minor_revision))
55            # branch to match all major branches, like v3.x or v3
56            candidates.append('release/v{}.x'.format(major_revision))
57            candidates.append('release/v{}'.format(major_revision))
58    except subprocess.CalledProcessError:
59        # this should not happen as IDF should have describe message
60        pass
61
62    return [c for c in candidates if c]  # filter out null value
63
64
65if __name__ == '__main__':
66    parser = argparse.ArgumentParser()
67    parser.add_argument('project',
68                        help='the name of project')
69    parser.add_argument('project_relative_path',
70                        help='relative path of project to IDF repository directory')
71    parser.add_argument('--customized_only', action='store_true',
72                        help='Only to find customized revision')
73
74    args = parser.parse_args()
75
76    if args.customized_only:
77        customized_revision = get_customized_project_revision(args.project)
78        candidate_branches = [customized_revision] if customized_revision else []
79    else:
80        candidate_branches = target_branch_candidates(args.project)
81
82    # change to project dir for checkout
83    os.chdir(args.project_relative_path)
84
85    ref_to_use = ''
86    for candidate in candidate_branches:
87        # check if the branch, tag or commit exists
88        try:
89            subprocess.check_call(['git', 'cat-file', '-t', 'origin/{}'.format(candidate)], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
90            ref_to_use = candidate
91            break
92        except subprocess.CalledProcessError:
93            try:
94                # For customized commits
95                subprocess.check_call(['git', 'cat-file', '-t', candidate], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
96                ref_to_use = candidate
97                break
98            except subprocess.CalledProcessError:
99                pass
100            continue
101
102    if ref_to_use:
103        for _ in range(RETRY_COUNT):
104            # Add retry for projects with git-lfs
105            try:
106                subprocess.check_call(['git', 'checkout', '-f', ref_to_use], stdout=subprocess.PIPE)  # not print the stdout
107                print('CI using ref {} for project {}'.format(ref_to_use, args.project))
108                break
109            except subprocess.CalledProcessError:
110                pass
111        else:
112            print('Failed to use ref {} for project {}'.format(ref_to_use, args.project))
113            exit(1)
114    else:
115        print('using default branch')
116