summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa
diff options
context:
space:
mode:
authorRichard Purdie <richard.purdie@linuxfoundation.org>2019-02-17 22:12:40 +0000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2019-02-18 10:33:13 +0000
commit18880a9f1cbc94b8c963f636e9756a1020fc5575 (patch)
treee2d80d3e29823ba6ea40c877539d12761f202d78 /meta/lib/oeqa
parentc71dc290b0b9e634f6f2e9475928628137e8e565 (diff)
downloadpoky-18880a9f1cbc94b8c963f636e9756a1020fc5575.tar.gz
scripts/oe-git-archive: Separate out functionality to library function
This turns the core of the script into a library function. Ultimately this will let us call that code with custom 'keywords' rather than relying on the data parsed from bitbake metadata which can't be used when archiving historical results. (From OE-Core rev: 4820ca2b0850e29b04a4fd5659a6e9837d6714d0) Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/lib/oeqa')
-rw-r--r--meta/lib/oeqa/utils/gitarchive.py173
1 files changed, 173 insertions, 0 deletions
diff --git a/meta/lib/oeqa/utils/gitarchive.py b/meta/lib/oeqa/utils/gitarchive.py
new file mode 100644
index 0000000000..fddd608593
--- /dev/null
+++ b/meta/lib/oeqa/utils/gitarchive.py
@@ -0,0 +1,173 @@
1#
2# Helper functions for committing data to git and pushing upstream
3#
4# Copyright (c) 2017, Intel Corporation.
5# Copyright (c) 2019, Linux Foundation
6#
7# This program is free software; you can redistribute it and/or modify it
8# under the terms and conditions of the GNU General Public License,
9# version 2, as published by the Free Software Foundation.
10#
11# This program is distributed in the hope it will be useful, but WITHOUT
12# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14# more details.
15#
16
17import os
18import re
19import sys
20from oeqa.utils.git import GitRepo, GitError
21
22class ArchiveError(Exception):
23 """Internal error handling of this script"""
24
25def format_str(string, fields):
26 """Format string using the given fields (dict)"""
27 try:
28 return string.format(**fields)
29 except KeyError as err:
30 raise ArchiveError("Unable to expand string '{}': unknown field {} "
31 "(valid fields are: {})".format(
32 string, err, ', '.join(sorted(fields.keys()))))
33
34
35def init_git_repo(path, no_create, bare, log):
36 """Initialize local Git repository"""
37 path = os.path.abspath(path)
38 if os.path.isfile(path):
39 raise ArchiveError("Invalid Git repo at {}: path exists but is not a "
40 "directory".format(path))
41 if not os.path.isdir(path) or not os.listdir(path):
42 if no_create:
43 raise ArchiveError("No git repo at {}, refusing to create "
44 "one".format(path))
45 if not os.path.isdir(path):
46 try:
47 os.mkdir(path)
48 except (FileNotFoundError, PermissionError) as err:
49 raise ArchiveError("Failed to mkdir {}: {}".format(path, err))
50 if not os.listdir(path):
51 log.info("Initializing a new Git repo at %s", path)
52 repo = GitRepo.init(path, bare)
53 try:
54 repo = GitRepo(path, is_topdir=True)
55 except GitError:
56 raise ArchiveError("Non-empty directory that is not a Git repository "
57 "at {}\nPlease specify an existing Git repository, "
58 "an empty directory or a non-existing directory "
59 "path.".format(path))
60 return repo
61
62
63def git_commit_data(repo, data_dir, branch, message, exclude, notes, log):
64 """Commit data into a Git repository"""
65 log.info("Committing data into to branch %s", branch)
66 tmp_index = os.path.join(repo.git_dir, 'index.oe-git-archive')
67 try:
68 # Create new tree object from the data
69 env_update = {'GIT_INDEX_FILE': tmp_index,
70 'GIT_WORK_TREE': os.path.abspath(data_dir)}
71 repo.run_cmd('add .', env_update)
72
73 # Remove files that are excluded
74 if exclude:
75 repo.run_cmd(['rm', '--cached'] + [f for f in exclude], env_update)
76
77 tree = repo.run_cmd('write-tree', env_update)
78
79 # Create new commit object from the tree
80 parent = repo.rev_parse(branch)
81 git_cmd = ['commit-tree', tree, '-m', message]
82 if parent:
83 git_cmd += ['-p', parent]
84 commit = repo.run_cmd(git_cmd, env_update)
85
86 # Create git notes
87 for ref, filename in notes:
88 ref = ref.format(branch_name=branch)
89 repo.run_cmd(['notes', '--ref', ref, 'add',
90 '-F', os.path.abspath(filename), commit])
91
92 # Update branch head
93 git_cmd = ['update-ref', 'refs/heads/' + branch, commit]
94 if parent:
95 git_cmd.append(parent)
96 repo.run_cmd(git_cmd)
97
98 # Update current HEAD, if we're on branch 'branch'
99 if not repo.bare and repo.get_current_branch() == branch:
100 log.info("Updating %s HEAD to latest commit", repo.top_dir)
101 repo.run_cmd('reset --hard')
102
103 return commit
104 finally:
105 if os.path.exists(tmp_index):
106 os.unlink(tmp_index)
107
108
109def expand_tag_strings(repo, name_pattern, msg_subj_pattern, msg_body_pattern,
110 keywords):
111 """Generate tag name and message, with support for running id number"""
112 keyws = keywords.copy()
113 # Tag number is handled specially: if not defined, we autoincrement it
114 if 'tag_number' not in keyws:
115 # Fill in all other fields than 'tag_number'
116 keyws['tag_number'] = '{tag_number}'
117 tag_re = format_str(name_pattern, keyws)
118 # Replace parentheses for proper regex matching
119 tag_re = tag_re.replace('(', '\(').replace(')', '\)') + '$'
120 # Inject regex group pattern for 'tag_number'
121 tag_re = tag_re.format(tag_number='(?P<tag_number>[0-9]{1,5})')
122
123 keyws['tag_number'] = 0
124 for existing_tag in repo.run_cmd('tag').splitlines():
125 match = re.match(tag_re, existing_tag)
126
127 if match and int(match.group('tag_number')) >= keyws['tag_number']:
128 keyws['tag_number'] = int(match.group('tag_number')) + 1
129
130 tag_name = format_str(name_pattern, keyws)
131 msg_subj= format_str(msg_subj_pattern.strip(), keyws)
132 msg_body = format_str(msg_body_pattern, keyws)
133 return tag_name, msg_subj + '\n\n' + msg_body
134
135def gitarchive(data_dir, git_dir, no_create, bare, commit_msg_subject, commit_msg_body, branch_name, no_tag, tagname, tag_msg_subject, tag_msg_body, exclude, notes, push, keywords, log):
136
137 if not os.path.isdir(data_dir):
138 raise ArchiveError("Not a directory: {}".format(data_dir))
139
140 data_repo = init_git_repo(git_dir, no_create, bare, log)
141
142 # Expand strings early in order to avoid getting into inconsistent
143 # state (e.g. no tag even if data was committed)
144 commit_msg = format_str(commit_msg_subject.strip(), keywords)
145 commit_msg += '\n\n' + format_str(commit_msg_body, keywords)
146 branch_name = format_str(branch_name, keywords)
147 tag_name = None
148 if not no_tag and tagname:
149 tag_name, tag_msg = expand_tag_strings(data_repo, tagname,
150 tag_msg_subject,
151 tag_msg_body, keywords)
152
153 # Commit data
154 commit = git_commit_data(data_repo, data_dir, branch_name,
155 commit_msg, exclude, notes, log)
156
157 # Create tag
158 if tag_name:
159 log.info("Creating tag %s", tag_name)
160 data_repo.run_cmd(['tag', '-a', '-m', tag_msg, tag_name, commit])
161
162 # Push data to remote
163 if push:
164 cmd = ['push', '--tags']
165 # If no remote is given we push with the default settings from
166 # gitconfig
167 if push is not True:
168 notes_refs = ['refs/notes/' + ref.format(branch_name=branch_name)
169 for ref, _ in notes]
170 cmd.extend([push, branch_name] + notes_refs)
171 log.info("Pushing data to remote")
172 data_repo.run_cmd(cmd)
173