blob: e5a466c8cc65f89646dac436e306d8cb7efd65f5 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass26132882012-01-14 15:12:45 +00002# Copyright (c) 2011 The Chromium OS Authors.
3#
Simon Glass26132882012-01-14 15:12:45 +00004
Simon Glass26132882012-01-14 15:12:45 +00005import os
Simon Glass26132882012-01-14 15:12:45 +00006import sys
Simon Glass26132882012-01-14 15:12:45 +00007
Simon Glass131444f2023-02-23 18:18:04 -07008from u_boot_pylib import command
9from u_boot_pylib import terminal
Simon Glass11aba512012-12-15 10:42:07 +000010
Simon Glass761648b2022-01-29 14:14:11 -070011# True to use --no-decorate - we check this in setup()
Simon Glass7ade94e2025-03-16 08:00:18 +000012USE_NO_DECORATE = True
Simon Glass6af913d2014-08-09 15:33:11 -060013
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -050014
Simon Glass761648b2022-01-29 14:14:11 -070015def log_cmd(commit_range, git_dir=None, oneline=False, reverse=False,
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -050016 count=None):
Simon Glassb9dbcb42014-08-09 15:33:10 -060017 """Create a command to perform a 'git log'
18
19 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +000020 commit_range (str): Range expression to use for log, None for none
21 git_dir (str): Path to git repository (None to use default)
22 oneline (bool): True to use --oneline, else False
23 reverse (bool): True to reverse the log (--reverse)
24 count (int or None): Number of commits to list, or None for no limit
Simon Glassb9dbcb42014-08-09 15:33:10 -060025 Return:
26 List containing command and arguments to run
27 """
28 cmd = ['git']
29 if git_dir:
30 cmd += ['--git-dir', git_dir]
Simon Glass5f4e00d2014-08-28 09:43:37 -060031 cmd += ['--no-pager', 'log', '--no-color']
Simon Glassb9dbcb42014-08-09 15:33:10 -060032 if oneline:
33 cmd.append('--oneline')
Simon Glass7ade94e2025-03-16 08:00:18 +000034 if USE_NO_DECORATE:
Simon Glass6af913d2014-08-09 15:33:11 -060035 cmd.append('--no-decorate')
Simon Glass299b9092014-08-14 21:59:11 -060036 if reverse:
37 cmd.append('--reverse')
Simon Glassb9dbcb42014-08-09 15:33:10 -060038 if count is not None:
Simon Glass7ade94e2025-03-16 08:00:18 +000039 cmd.append(f'-n{count}')
Simon Glassb9dbcb42014-08-09 15:33:10 -060040 if commit_range:
41 cmd.append(commit_range)
Simon Glass642e9a62016-03-12 18:50:31 -070042
43 # Add this in case we have a branch with the same name as a directory.
44 # This avoids messages like this, for example:
45 # fatal: ambiguous argument 'test': both revision and filename
46 cmd.append('--')
Simon Glassb9dbcb42014-08-09 15:33:10 -060047 return cmd
Simon Glass26132882012-01-14 15:12:45 +000048
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -050049
Simon Glass761648b2022-01-29 14:14:11 -070050def count_commits_to_branch(branch):
Simon Glass26132882012-01-14 15:12:45 +000051 """Returns number of commits between HEAD and the tracking branch.
52
53 This looks back to the tracking branch and works out the number of commits
54 since then.
55
Simon Glass2eb4da72020-07-05 21:41:51 -060056 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +000057 branch (str or None): Branch to count from (None for current branch)
Simon Glass2eb4da72020-07-05 21:41:51 -060058
Simon Glass26132882012-01-14 15:12:45 +000059 Return:
60 Number of patches that exist on top of the branch
61 """
Simon Glass2eb4da72020-07-05 21:41:51 -060062 if branch:
Simon Glass7ade94e2025-03-16 08:00:18 +000063 us, _ = get_upstream('.git', branch)
64 rev_range = f'{us}..{branch}'
Simon Glass2eb4da72020-07-05 21:41:51 -060065 else:
66 rev_range = '@{upstream}..'
Simon Glass51f55182025-02-03 09:26:45 -070067 cmd = log_cmd(rev_range, oneline=True)
68 result = command.run_one(*cmd, capture=True, capture_stderr=True,
69 oneline=True, raise_on_error=False)
Simon Glass1c1f2072020-10-29 21:46:34 -060070 if result.return_code:
Simon Glass7ade94e2025-03-16 08:00:18 +000071 raise ValueError(
72 f'Failed to determine upstream: {result.stderr.strip()}')
Simon Glass1c1f2072020-10-29 21:46:34 -060073 patch_count = len(result.stdout.splitlines())
Simon Glass26132882012-01-14 15:12:45 +000074 return patch_count
75
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -050076
Simon Glass761648b2022-01-29 14:14:11 -070077def name_revision(commit_hash):
Simon Glassf204ab12014-12-01 17:33:54 -070078 """Gets the revision name for a commit
79
80 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +000081 commit_hash (str): Commit hash to look up
Simon Glassf204ab12014-12-01 17:33:54 -070082
83 Return:
84 Name of revision, if any, else None
85 """
Simon Glass51f55182025-02-03 09:26:45 -070086 stdout = command.output_one_line('git', 'name-rev', commit_hash)
Simon Glassf204ab12014-12-01 17:33:54 -070087
88 # We expect a commit, a space, then a revision name
89 name = stdout.split(' ')[1].strip()
90 return name
91
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -050092
Simon Glass761648b2022-01-29 14:14:11 -070093def guess_upstream(git_dir, branch):
Simon Glassf204ab12014-12-01 17:33:54 -070094 """Tries to guess the upstream for a branch
95
96 This lists out top commits on a branch and tries to find a suitable
97 upstream. It does this by looking for the first commit where
98 'git name-rev' returns a plain branch name, with no ! or ^ modifiers.
99
100 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000101 git_dir (str): Git directory containing repo
102 branch (str): Name of branch
Simon Glassf204ab12014-12-01 17:33:54 -0700103
104 Returns:
105 Tuple:
106 Name of upstream branch (e.g. 'upstream/master') or None if none
107 Warning/error message, or None if none
108 """
Simon Glass51f55182025-02-03 09:26:45 -0700109 cmd = log_cmd(branch, git_dir=git_dir, oneline=True, count=100)
110 result = command.run_one(*cmd, capture=True, capture_stderr=True,
111 raise_on_error=False)
Simon Glassf204ab12014-12-01 17:33:54 -0700112 if result.return_code:
Simon Glass7ade94e2025-03-16 08:00:18 +0000113 return None, f"Branch '{branch}' not found"
Simon Glassf204ab12014-12-01 17:33:54 -0700114 for line in result.stdout.splitlines()[1:]:
115 commit_hash = line.split(' ')[0]
Simon Glass761648b2022-01-29 14:14:11 -0700116 name = name_revision(commit_hash)
Simon Glassf204ab12014-12-01 17:33:54 -0700117 if '~' not in name and '^' not in name:
118 if name.startswith('remotes/'):
119 name = name[8:]
Simon Glass7ade94e2025-03-16 08:00:18 +0000120 return name, f"Guessing upstream as '{name}'"
121 return None, f"Cannot find a suitable upstream for branch '{branch}'"
Simon Glassf204ab12014-12-01 17:33:54 -0700122
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500123
Simon Glass761648b2022-01-29 14:14:11 -0700124def get_upstream(git_dir, branch):
Simon Glass11aba512012-12-15 10:42:07 +0000125 """Returns the name of the upstream for a branch
126
127 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000128 git_dir (str): Git directory containing repo
129 branch (str): Name of branch
Simon Glass11aba512012-12-15 10:42:07 +0000130
131 Returns:
Simon Glassf204ab12014-12-01 17:33:54 -0700132 Tuple:
133 Name of upstream branch (e.g. 'upstream/master') or None if none
134 Warning/error message, or None if none
Simon Glass11aba512012-12-15 10:42:07 +0000135 """
Simon Glassd2e95382013-05-08 08:06:08 +0000136 try:
Simon Glass840be732022-01-29 14:14:05 -0700137 remote = command.output_one_line('git', '--git-dir', git_dir, 'config',
Simon Glass7ade94e2025-03-16 08:00:18 +0000138 f'branch.{branch}.remote')
Simon Glass840be732022-01-29 14:14:05 -0700139 merge = command.output_one_line('git', '--git-dir', git_dir, 'config',
Simon Glass7ade94e2025-03-16 08:00:18 +0000140 f'branch.{branch}.merge')
Simon Glass82327692025-02-03 09:26:43 -0700141 except command.CommandExc:
Simon Glass761648b2022-01-29 14:14:11 -0700142 upstream, msg = guess_upstream(git_dir, branch)
Simon Glassf204ab12014-12-01 17:33:54 -0700143 return upstream, msg
Simon Glassd2e95382013-05-08 08:06:08 +0000144
Simon Glass11aba512012-12-15 10:42:07 +0000145 if remote == '.':
Simon Glass7e92f5c2015-01-29 11:35:16 -0700146 return merge, None
Simon Glass7ade94e2025-03-16 08:00:18 +0000147 if remote and merge:
Simon Glass978c1fb2023-10-30 10:22:30 -0700148 # Drop the initial refs/heads from merge
149 leaf = merge.split('/', maxsplit=2)[2:]
Simon Glass7ade94e2025-03-16 08:00:18 +0000150 return f'{remote}/{"/".join(leaf)}', None
151 raise ValueError("Cannot determine upstream branch for branch "
152 f"'{branch}' remote='{remote}', merge='{merge}'")
Simon Glass11aba512012-12-15 10:42:07 +0000153
154
Simon Glass761648b2022-01-29 14:14:11 -0700155def get_range_in_branch(git_dir, branch, include_upstream=False):
Simon Glass11aba512012-12-15 10:42:07 +0000156 """Returns an expression for the commits in the given branch.
157
158 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000159 git_dir (str): Directory containing git repo
160 branch (str): Name of branch
161 include_upstream (bool): Include the upstream commit as well
Simon Glass11aba512012-12-15 10:42:07 +0000162 Return:
163 Expression in the form 'upstream..branch' which can be used to
Simon Glassd2e95382013-05-08 08:06:08 +0000164 access the commits. If the branch does not exist, returns None.
Simon Glass11aba512012-12-15 10:42:07 +0000165 """
Simon Glass761648b2022-01-29 14:14:11 -0700166 upstream, msg = get_upstream(git_dir, branch)
Simon Glassd2e95382013-05-08 08:06:08 +0000167 if not upstream:
Simon Glassf204ab12014-12-01 17:33:54 -0700168 return None, msg
Simon Glass7ade94e2025-03-16 08:00:18 +0000169 rstr = f"{upstream}{'~' if include_upstream else ''}..{branch}"
Simon Glassf204ab12014-12-01 17:33:54 -0700170 return rstr, msg
Simon Glass11aba512012-12-15 10:42:07 +0000171
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500172
Simon Glass761648b2022-01-29 14:14:11 -0700173def count_commits_in_range(git_dir, range_expr):
Simon Glass5eeef462014-12-01 17:33:57 -0700174 """Returns the number of commits in the given range.
175
176 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000177 git_dir (str): Directory containing git repo
178 range_expr (str): Range to check
Simon Glass5eeef462014-12-01 17:33:57 -0700179 Return:
Anatolij Gustschinf2bcb322019-10-27 17:55:04 +0100180 Number of patches that exist in the supplied range or None if none
Simon Glass5eeef462014-12-01 17:33:57 -0700181 were found
182 """
Simon Glass51f55182025-02-03 09:26:45 -0700183 cmd = log_cmd(range_expr, git_dir=git_dir, oneline=True)
184 result = command.run_one(*cmd, capture=True, capture_stderr=True,
185 raise_on_error=False)
Simon Glass5eeef462014-12-01 17:33:57 -0700186 if result.return_code:
Simon Glass7ade94e2025-03-16 08:00:18 +0000187 return None, f"Range '{range_expr}' not found or is invalid"
Simon Glass5eeef462014-12-01 17:33:57 -0700188 patch_count = len(result.stdout.splitlines())
189 return patch_count, None
190
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500191
Simon Glass761648b2022-01-29 14:14:11 -0700192def count_commits_in_branch(git_dir, branch, include_upstream=False):
Simon Glass11aba512012-12-15 10:42:07 +0000193 """Returns the number of commits in the given branch.
194
195 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000196 git_dir (str): Directory containing git repo
197 branch (str): Name of branch
198 include_upstream (bool): Include the upstream commit as well
Simon Glass11aba512012-12-15 10:42:07 +0000199 Return:
Simon Glassd2e95382013-05-08 08:06:08 +0000200 Number of patches that exist on top of the branch, or None if the
201 branch does not exist.
Simon Glass11aba512012-12-15 10:42:07 +0000202 """
Simon Glass761648b2022-01-29 14:14:11 -0700203 range_expr, msg = get_range_in_branch(git_dir, branch, include_upstream)
Simon Glassd2e95382013-05-08 08:06:08 +0000204 if not range_expr:
Simon Glassf204ab12014-12-01 17:33:54 -0700205 return None, msg
Simon Glass761648b2022-01-29 14:14:11 -0700206 return count_commits_in_range(git_dir, range_expr)
Simon Glass11aba512012-12-15 10:42:07 +0000207
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500208
Simon Glass761648b2022-01-29 14:14:11 -0700209def count_commits(commit_range):
Simon Glass11aba512012-12-15 10:42:07 +0000210 """Returns the number of commits in the given range.
211
212 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000213 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
Simon Glass11aba512012-12-15 10:42:07 +0000214 Return:
215 Number of patches that exist on top of the branch
216 """
Simon Glass761648b2022-01-29 14:14:11 -0700217 pipe = [log_cmd(commit_range, oneline=True),
Simon Glass11aba512012-12-15 10:42:07 +0000218 ['wc', '-l']]
Simon Glass840be732022-01-29 14:14:05 -0700219 stdout = command.run_pipe(pipe, capture=True, oneline=True).stdout
Simon Glass11aba512012-12-15 10:42:07 +0000220 patch_count = int(stdout)
221 return patch_count
222
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500223
Simon Glass761648b2022-01-29 14:14:11 -0700224def checkout(commit_hash, git_dir=None, work_tree=None, force=False):
Simon Glass11aba512012-12-15 10:42:07 +0000225 """Checkout the selected commit for this build
226
227 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000228 commit_hash (str): Commit hash to check out
229 git_dir (str): Directory containing git repo, or None for current dir
230 work_tree (str): Git worktree to use, or None if none
231 force (bool): True to force the checkout (git checkout -f)
Simon Glass11aba512012-12-15 10:42:07 +0000232 """
233 pipe = ['git']
234 if git_dir:
235 pipe.extend(['--git-dir', git_dir])
236 if work_tree:
237 pipe.extend(['--work-tree', work_tree])
238 pipe.append('checkout')
239 if force:
240 pipe.append('-f')
241 pipe.append(commit_hash)
Simon Glass840be732022-01-29 14:14:05 -0700242 result = command.run_pipe([pipe], capture=True, raise_on_error=False,
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500243 capture_stderr=True)
Simon Glass11aba512012-12-15 10:42:07 +0000244 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000245 raise OSError(f'git checkout ({pipe}): {result.stderr}')
Simon Glass11aba512012-12-15 10:42:07 +0000246
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500247
Simon Glass7ade94e2025-03-16 08:00:18 +0000248def clone(repo, output_dir):
249 """Clone a repo
Simon Glass11aba512012-12-15 10:42:07 +0000250
251 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000252 repo (str): Repo to clone (e.g. web address)
253 output_dir (str): Directory to close into
Simon Glass11aba512012-12-15 10:42:07 +0000254 """
Simon Glass7ade94e2025-03-16 08:00:18 +0000255 result = command.run_one('git', 'clone', repo, '.', capture=True,
Simon Glass51f55182025-02-03 09:26:45 -0700256 cwd=output_dir, capture_stderr=True)
Simon Glass11aba512012-12-15 10:42:07 +0000257 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000258 raise OSError(f'git clone: {result.stderr}')
Simon Glass11aba512012-12-15 10:42:07 +0000259
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500260
Simon Glass761648b2022-01-29 14:14:11 -0700261def fetch(git_dir=None, work_tree=None):
Simon Glass11aba512012-12-15 10:42:07 +0000262 """Fetch from the origin repo
263
264 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000265 git_dir (str): Directory containing git repo, or None for current dir
266 work_tree (str or None): Git worktree to use, or None if none
Simon Glass11aba512012-12-15 10:42:07 +0000267 """
Simon Glass51f55182025-02-03 09:26:45 -0700268 cmd = ['git']
Simon Glass11aba512012-12-15 10:42:07 +0000269 if git_dir:
Simon Glass51f55182025-02-03 09:26:45 -0700270 cmd.extend(['--git-dir', git_dir])
Simon Glass11aba512012-12-15 10:42:07 +0000271 if work_tree:
Simon Glass51f55182025-02-03 09:26:45 -0700272 cmd.extend(['--work-tree', work_tree])
273 cmd.append('fetch')
274 result = command.run_one(*cmd, capture=True, capture_stderr=True)
Simon Glass11aba512012-12-15 10:42:07 +0000275 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000276 raise OSError(f'git fetch: {result.stderr}')
Simon Glass11aba512012-12-15 10:42:07 +0000277
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500278
Simon Glass761648b2022-01-29 14:14:11 -0700279def check_worktree_is_available(git_dir):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300280 """Check if git-worktree functionality is available
281
282 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000283 git_dir (str): The repository to test in
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300284
285 Returns:
286 True if git-worktree commands will work, False otherwise.
287 """
Simon Glass51f55182025-02-03 09:26:45 -0700288 result = command.run_one('git', '--git-dir', git_dir, 'worktree', 'list',
289 capture=True, capture_stderr=True,
290 raise_on_error=False)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300291 return result.return_code == 0
292
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500293
Simon Glass761648b2022-01-29 14:14:11 -0700294def add_worktree(git_dir, output_dir, commit_hash=None):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300295 """Create and checkout a new git worktree for this build
296
297 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000298 git_dir (str): The repository to checkout the worktree from
299 output_dir (str): Path for the new worktree
300 commit_hash (str): Commit hash to checkout
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300301 """
302 # We need to pass --detach to avoid creating a new branch
Simon Glass51f55182025-02-03 09:26:45 -0700303 cmd = ['git', '--git-dir', git_dir, 'worktree', 'add', '.', '--detach']
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300304 if commit_hash:
Simon Glass51f55182025-02-03 09:26:45 -0700305 cmd.append(commit_hash)
306 result = command.run_one(*cmd, capture=True, cwd=output_dir,
307 capture_stderr=True)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300308 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000309 raise OSError(f'git worktree add: {result.stderr}')
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300310
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500311
Simon Glass761648b2022-01-29 14:14:11 -0700312def prune_worktrees(git_dir):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300313 """Remove administrative files for deleted worktrees
314
315 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000316 git_dir (str): The repository whose deleted worktrees should be pruned
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300317 """
Simon Glass51f55182025-02-03 09:26:45 -0700318 result = command.run_one('git', '--git-dir', git_dir, 'worktree', 'prune',
319 capture=True, capture_stderr=True)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300320 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000321 raise OSError(f'git worktree prune: {result.stderr}')
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300322
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500323
324def create_patches(branch, start, count, ignore_binary, series, signoff=True):
Simon Glass26132882012-01-14 15:12:45 +0000325 """Create a series of patches from the top of the current branch.
326
327 The patch files are written to the current directory using
328 git format-patch.
329
330 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000331 branch (str): Branch to create patches from (None for current branch)
332 start (int): Commit to start from: 0=HEAD, 1=next one, etc.
333 count (int): number of commits to include
334 ignore_binary (bool): Don't generate patches for binary files
335 series (Series): Series object for this series (set of patches)
336 signoff (bool): True to add signoff lines automatically
Simon Glass26132882012-01-14 15:12:45 +0000337 Return:
Simon Glass24725af2020-07-05 21:41:49 -0600338 Filename of cover letter (None if none)
Simon Glass26132882012-01-14 15:12:45 +0000339 List of filenames of patch files
340 """
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500341 cmd = ['git', 'format-patch', '-M']
Philipp Tomsich858531a2020-11-24 18:14:52 +0100342 if signoff:
343 cmd.append('--signoff')
Bin Menga04f1212020-05-04 00:52:44 -0700344 if ignore_binary:
345 cmd.append('--no-binary')
Simon Glass26132882012-01-14 15:12:45 +0000346 if series.get('cover'):
347 cmd.append('--cover-letter')
348 prefix = series.GetPatchPrefix()
349 if prefix:
Simon Glass7ade94e2025-03-16 08:00:18 +0000350 cmd += [f'--subject-prefix={prefix}']
Simon Glass2eb4da72020-07-05 21:41:51 -0600351 brname = branch or 'HEAD'
Simon Glass7ade94e2025-03-16 08:00:18 +0000352 cmd += [f'{brname}~{start + count}..{brname}~{start}']
Simon Glass26132882012-01-14 15:12:45 +0000353
Simon Glass840be732022-01-29 14:14:05 -0700354 stdout = command.run_list(cmd)
Simon Glass26132882012-01-14 15:12:45 +0000355 files = stdout.splitlines()
356
357 # We have an extra file if there is a cover letter
358 if series.get('cover'):
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500359 return files[0], files[1:]
Simon Glass7ade94e2025-03-16 08:00:18 +0000360 return None, files
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500361
Simon Glass26132882012-01-14 15:12:45 +0000362
Simon Glassdea1ddf2025-04-07 22:51:44 +1200363def build_email_list(in_list, alias, tag=None, warn_on_error=True):
Simon Glass26132882012-01-14 15:12:45 +0000364 """Build a list of email addresses based on an input list.
365
366 Takes a list of email addresses and aliases, and turns this into a list
367 of only email address, by resolving any aliases that are present.
368
369 If the tag is given, then each email address is prepended with this
370 tag and a space. If the tag starts with a minus sign (indicating a
371 command line parameter) then the email address is quoted.
372
373 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000374 in_list (list of str): List of aliases/email addresses
Simon Glass7ade94e2025-03-16 08:00:18 +0000375 alias (dict): Alias dictionary:
376 key: alias
377 value: list of aliases or email addresses
Simon Glassdea1ddf2025-04-07 22:51:44 +1200378 tag (str): Text to put before each address
Simon Glass7ade94e2025-03-16 08:00:18 +0000379 warn_on_error (bool): True to raise an error when an alias fails to
380 match, False to just print a message.
Simon Glass26132882012-01-14 15:12:45 +0000381
382 Returns:
383 List of email addresses
384
385 >>> alias = {}
386 >>> alias['fred'] = ['f.bloggs@napier.co.nz']
387 >>> alias['john'] = ['j.bloggs@napier.co.nz']
388 >>> alias['mary'] = ['Mary Poppins <m.poppins@cloud.net>']
389 >>> alias['boys'] = ['fred', ' john']
390 >>> alias['all'] = ['fred ', 'john', ' mary ']
Simon Glassdea1ddf2025-04-07 22:51:44 +1200391 >>> build_email_list(['john', 'mary'], alias, None)
Simon Glass26132882012-01-14 15:12:45 +0000392 ['j.bloggs@napier.co.nz', 'Mary Poppins <m.poppins@cloud.net>']
Simon Glassdea1ddf2025-04-07 22:51:44 +1200393 >>> build_email_list(['john', 'mary'], alias, '--to')
Simon Glass26132882012-01-14 15:12:45 +0000394 ['--to "j.bloggs@napier.co.nz"', \
395'--to "Mary Poppins <m.poppins@cloud.net>"']
Simon Glassdea1ddf2025-04-07 22:51:44 +1200396 >>> build_email_list(['john', 'mary'], alias, 'Cc')
Simon Glass26132882012-01-14 15:12:45 +0000397 ['Cc j.bloggs@napier.co.nz', 'Cc Mary Poppins <m.poppins@cloud.net>']
398 """
Simon Glass26132882012-01-14 15:12:45 +0000399 raw = []
400 for item in in_list:
Simon Glass761648b2022-01-29 14:14:11 -0700401 raw += lookup_email(item, alias, warn_on_error=warn_on_error)
Simon Glass26132882012-01-14 15:12:45 +0000402 result = []
403 for item in raw:
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500404 if item not in result:
Simon Glass26132882012-01-14 15:12:45 +0000405 result.append(item)
406 if tag:
Simon Glassa8ba0792025-05-08 04:38:30 +0200407 return [x for email in result for x in (tag, email)]
Simon Glass26132882012-01-14 15:12:45 +0000408 return result
409
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500410
Simon Glass761648b2022-01-29 14:14:11 -0700411def check_suppress_cc_config():
Nicolas Boichat0da95742020-07-13 10:50:00 +0800412 """Check if sendemail.suppresscc is configured correctly.
413
414 Returns:
Simon Glass7ade94e2025-03-16 08:00:18 +0000415 bool: True if the option is configured correctly, False otherwise.
Nicolas Boichat0da95742020-07-13 10:50:00 +0800416 """
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500417 suppresscc = command.output_one_line(
418 'git', 'config', 'sendemail.suppresscc', raise_on_error=False)
Nicolas Boichat0da95742020-07-13 10:50:00 +0800419
420 # Other settings should be fine.
Simon Glass7ade94e2025-03-16 08:00:18 +0000421 if suppresscc in ('all', 'cccmd'):
Nicolas Boichat0da95742020-07-13 10:50:00 +0800422 col = terminal.Color()
423
Simon Glass7ade94e2025-03-16 08:00:18 +0000424 print(col.build(col.RED, 'error') +
425 f': git config sendemail.suppresscc set to {suppresscc}\n' +
426 ' patman needs --cc-cmd to be run to set the cc list.\n' +
427 ' Please run:\n' +
428 ' git config --unset sendemail.suppresscc\n' +
429 ' Or read the man page:\n' +
430 ' git send-email --help\n' +
431 ' and set an option that runs --cc-cmd\n')
Nicolas Boichat0da95742020-07-13 10:50:00 +0800432 return False
433
434 return True
435
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500436
Simon Glass761648b2022-01-29 14:14:11 -0700437def email_patches(series, cover_fname, args, dry_run, warn_on_error, cc_fname,
Simon Glass5efa3662025-04-07 22:51:45 +1200438 alias, self_only=False, in_reply_to=None, thread=False,
Simon Glass7ade94e2025-03-16 08:00:18 +0000439 smtp_server=None):
Simon Glass26132882012-01-14 15:12:45 +0000440 """Email a patch series.
441
442 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000443 series (Series): Series object containing destination info
444 cover_fname (str or None): filename of cover letter
445 args (list of str): list of filenames of patch files
446 dry_run (bool): Just return the command that would be run
447 warn_on_error (bool): True to print a warning when an alias fails to
448 match, False to ignore it.
449 cc_fname (str): Filename of Cc file for per-commit Cc
Simon Glass5efa3662025-04-07 22:51:45 +1200450 alias (dict): Alias dictionary:
Simon Glass7ade94e2025-03-16 08:00:18 +0000451 key: alias
452 value: list of aliases or email addresses
Simon Glass5efa3662025-04-07 22:51:45 +1200453 self_only (bool): True to just email to yourself as a test
Simon Glass7ade94e2025-03-16 08:00:18 +0000454 in_reply_to (str or None): If set we'll pass this to git as
455 --in-reply-to - should be a message ID that this is in reply to.
456 thread (bool): True to add --thread to git send-email (make
Mateusz Kulikowski80c2ebc2016-01-14 20:37:41 +0100457 all patches reply to cover-letter or first patch in series)
Simon Glass7ade94e2025-03-16 08:00:18 +0000458 smtp_server (str or None): SMTP server to use to send patches
Simon Glass26132882012-01-14 15:12:45 +0000459
460 Returns:
461 Git command that was/would be run
462
Doug Anderson51d73212012-11-26 15:21:40 +0000463 # For the duration of this doctest pretend that we ran patman with ./patman
464 >>> _old_argv0 = sys.argv[0]
465 >>> sys.argv[0] = './patman'
466
Simon Glass26132882012-01-14 15:12:45 +0000467 >>> alias = {}
468 >>> alias['fred'] = ['f.bloggs@napier.co.nz']
469 >>> alias['john'] = ['j.bloggs@napier.co.nz']
470 >>> alias['mary'] = ['m.poppins@cloud.net']
471 >>> alias['boys'] = ['fred', ' john']
472 >>> alias['all'] = ['fred ', 'john', ' mary ']
473 >>> alias[os.getenv('USER')] = ['this-is-me@me.com']
Simon Glass794c2592020-06-07 06:45:47 -0600474 >>> series = {}
475 >>> series['to'] = ['fred']
476 >>> series['cc'] = ['mary']
Simon Glass761648b2022-01-29 14:14:11 -0700477 >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
Simon Glass12ea5f42013-03-26 13:09:42 +0000478 False, alias)
Simon Glass26132882012-01-14 15:12:45 +0000479 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
Simon Glass1ee91c12020-11-03 13:54:10 -0700480"m.poppins@cloud.net" --cc-cmd "./patman send --cc-cmd cc-fname" cover p1 p2'
Simon Glass761648b2022-01-29 14:14:11 -0700481 >>> email_patches(series, None, ['p1'], True, True, 'cc-fname', False, \
Simon Glass12ea5f42013-03-26 13:09:42 +0000482 alias)
Simon Glass26132882012-01-14 15:12:45 +0000483 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
Simon Glass1ee91c12020-11-03 13:54:10 -0700484"m.poppins@cloud.net" --cc-cmd "./patman send --cc-cmd cc-fname" p1'
Simon Glass794c2592020-06-07 06:45:47 -0600485 >>> series['cc'] = ['all']
Simon Glass761648b2022-01-29 14:14:11 -0700486 >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
Simon Glass12ea5f42013-03-26 13:09:42 +0000487 True, alias)
Simon Glass26132882012-01-14 15:12:45 +0000488 'git send-email --annotate --to "this-is-me@me.com" --cc-cmd "./patman \
Simon Glass1ee91c12020-11-03 13:54:10 -0700489send --cc-cmd cc-fname" cover p1 p2'
Simon Glass761648b2022-01-29 14:14:11 -0700490 >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
Simon Glass12ea5f42013-03-26 13:09:42 +0000491 False, alias)
Simon Glass26132882012-01-14 15:12:45 +0000492 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
493"f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \
Simon Glass1ee91c12020-11-03 13:54:10 -0700494"m.poppins@cloud.net" --cc-cmd "./patman send --cc-cmd cc-fname" cover p1 p2'
Doug Anderson51d73212012-11-26 15:21:40 +0000495
496 # Restore argv[0] since we clobbered it.
497 >>> sys.argv[0] = _old_argv0
Simon Glass26132882012-01-14 15:12:45 +0000498 """
Simon Glass5efa3662025-04-07 22:51:45 +1200499 to = build_email_list(series.get('to'), alias, '--to', warn_on_error)
Simon Glass26132882012-01-14 15:12:45 +0000500 if not to:
Simon Glass840be732022-01-29 14:14:05 -0700501 git_config_to = command.output('git', 'config', 'sendemail.to',
Simon Glassc55e0562016-07-25 18:59:00 -0600502 raise_on_error=False)
Masahiro Yamadad91f5b92014-07-18 14:23:20 +0900503 if not git_config_to:
Simon Glass23b8a192019-05-14 15:53:36 -0600504 print("No recipient.\n"
505 "Please add something like this to a commit\n"
506 "Series-to: Fred Bloggs <f.blogs@napier.co.nz>\n"
507 "Or do something like this\n"
508 "git config sendemail.to u-boot@lists.denx.de")
Simon Glass7ade94e2025-03-16 08:00:18 +0000509 return None
Simon Glass761648b2022-01-29 14:14:11 -0700510 cc = build_email_list(list(set(series.get('cc')) - set(series.get('to'))),
Simon Glass5efa3662025-04-07 22:51:45 +1200511 alias, '--cc', warn_on_error)
Simon Glass26132882012-01-14 15:12:45 +0000512 if self_only:
Simon Glass5efa3662025-04-07 22:51:45 +1200513 to = build_email_list([os.getenv('USER')], '--to', alias,
Simon Glassdea1ddf2025-04-07 22:51:44 +1200514 warn_on_error)
Simon Glass26132882012-01-14 15:12:45 +0000515 cc = []
516 cmd = ['git', 'send-email', '--annotate']
Simon Glass8137e302018-06-19 09:56:07 -0600517 if smtp_server:
Simon Glass7ade94e2025-03-16 08:00:18 +0000518 cmd.append(f'--smtp-server={smtp_server}')
Doug Anderson06f27ac2013-03-17 10:31:04 +0000519 if in_reply_to:
Simon Glass7ade94e2025-03-16 08:00:18 +0000520 cmd.append(f'--in-reply-to="{in_reply_to}"')
Mateusz Kulikowski80c2ebc2016-01-14 20:37:41 +0100521 if thread:
522 cmd.append('--thread')
Doug Anderson06f27ac2013-03-17 10:31:04 +0000523
Simon Glass26132882012-01-14 15:12:45 +0000524 cmd += to
525 cmd += cc
Simon Glassa8ba0792025-05-08 04:38:30 +0200526 cmd += ['--cc-cmd', f'{sys.argv[0]} send --cc-cmd {cc_fname}']
Simon Glass26132882012-01-14 15:12:45 +0000527 if cover_fname:
528 cmd.append(cover_fname)
529 cmd += args
Simon Glass26132882012-01-14 15:12:45 +0000530 if not dry_run:
Simon Glassa8ba0792025-05-08 04:38:30 +0200531 command.run(*cmd, capture=False, capture_stderr=False)
532 cmdstr = ' '.join([f'"{x}"' if ' ' in x and not '"' in x else x
533 for x in cmd])
Simon Glass47e308e2017-05-29 15:31:25 -0600534 return cmdstr
Simon Glass26132882012-01-14 15:12:45 +0000535
536
Simon Glass5efa3662025-04-07 22:51:45 +1200537def lookup_email(lookup_name, alias, warn_on_error=True, level=0):
Simon Glass26132882012-01-14 15:12:45 +0000538 """If an email address is an alias, look it up and return the full name
539
540 TODO: Why not just use git's own alias feature?
541
542 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000543 lookup_name (str): Alias or email address to look up
Simon Glass5efa3662025-04-07 22:51:45 +1200544 alias (dict): Alias dictionary
Simon Glass7ade94e2025-03-16 08:00:18 +0000545 key: alias
546 value: list of aliases or email addresses
547 warn_on_error (bool): True to print a warning when an alias fails to
548 match, False to ignore it.
549 level (int): Depth of alias stack, used to detect recusion/loops
Simon Glass26132882012-01-14 15:12:45 +0000550
551 Returns:
552 tuple:
553 list containing a list of email addresses
554
555 Raises:
556 OSError if a recursive alias reference was found
557 ValueError if an alias was not found
558
559 >>> alias = {}
560 >>> alias['fred'] = ['f.bloggs@napier.co.nz']
561 >>> alias['john'] = ['j.bloggs@napier.co.nz']
562 >>> alias['mary'] = ['m.poppins@cloud.net']
563 >>> alias['boys'] = ['fred', ' john', 'f.bloggs@napier.co.nz']
564 >>> alias['all'] = ['fred ', 'john', ' mary ']
565 >>> alias['loop'] = ['other', 'john', ' mary ']
566 >>> alias['other'] = ['loop', 'john', ' mary ']
Simon Glass761648b2022-01-29 14:14:11 -0700567 >>> lookup_email('mary', alias)
Simon Glass26132882012-01-14 15:12:45 +0000568 ['m.poppins@cloud.net']
Simon Glass761648b2022-01-29 14:14:11 -0700569 >>> lookup_email('arthur.wellesley@howe.ro.uk', alias)
Simon Glass26132882012-01-14 15:12:45 +0000570 ['arthur.wellesley@howe.ro.uk']
Simon Glass761648b2022-01-29 14:14:11 -0700571 >>> lookup_email('boys', alias)
Simon Glass26132882012-01-14 15:12:45 +0000572 ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz']
Simon Glass761648b2022-01-29 14:14:11 -0700573 >>> lookup_email('all', alias)
Simon Glass26132882012-01-14 15:12:45 +0000574 ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz', 'm.poppins@cloud.net']
Simon Glass761648b2022-01-29 14:14:11 -0700575 >>> lookup_email('odd', alias)
Simon Glass1f975b92021-01-23 08:56:15 -0700576 Alias 'odd' not found
577 []
Simon Glass761648b2022-01-29 14:14:11 -0700578 >>> lookup_email('loop', alias)
Simon Glass26132882012-01-14 15:12:45 +0000579 Traceback (most recent call last):
580 ...
581 OSError: Recursive email alias at 'other'
Simon Glass761648b2022-01-29 14:14:11 -0700582 >>> lookup_email('odd', alias, warn_on_error=False)
Simon Glass12ea5f42013-03-26 13:09:42 +0000583 []
584 >>> # In this case the loop part will effectively be ignored.
Simon Glass761648b2022-01-29 14:14:11 -0700585 >>> lookup_email('loop', alias, warn_on_error=False)
Simon Glassb0cd3412014-08-28 09:43:35 -0600586 Recursive email alias at 'other'
587 Recursive email alias at 'john'
588 Recursive email alias at 'mary'
Simon Glass12ea5f42013-03-26 13:09:42 +0000589 ['j.bloggs@napier.co.nz', 'm.poppins@cloud.net']
Simon Glass26132882012-01-14 15:12:45 +0000590 """
Simon Glass26132882012-01-14 15:12:45 +0000591 lookup_name = lookup_name.strip()
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500592 if '@' in lookup_name: # Perhaps a real email address
Simon Glass26132882012-01-14 15:12:45 +0000593 return [lookup_name]
594
595 lookup_name = lookup_name.lower()
Simon Glass12ea5f42013-03-26 13:09:42 +0000596 col = terminal.Color()
Simon Glass26132882012-01-14 15:12:45 +0000597
Simon Glass12ea5f42013-03-26 13:09:42 +0000598 out_list = []
Simon Glass26132882012-01-14 15:12:45 +0000599 if level > 10:
Simon Glass7ade94e2025-03-16 08:00:18 +0000600 msg = f"Recursive email alias at '{lookup_name}'"
Simon Glass1f975b92021-01-23 08:56:15 -0700601 if warn_on_error:
Paul Burtonf14a1312016-09-27 16:03:51 +0100602 raise OSError(msg)
Simon Glass7ade94e2025-03-16 08:00:18 +0000603 print(col.build(col.RED, msg))
604 return out_list
Simon Glass26132882012-01-14 15:12:45 +0000605
Simon Glass26132882012-01-14 15:12:45 +0000606 if lookup_name:
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500607 if lookup_name not in alias:
Simon Glass7ade94e2025-03-16 08:00:18 +0000608 msg = f"Alias '{lookup_name}' not found"
Simon Glass1f975b92021-01-23 08:56:15 -0700609 if warn_on_error:
Simon Glassf45d3742022-01-29 14:14:17 -0700610 print(col.build(col.RED, msg))
Simon Glass1f975b92021-01-23 08:56:15 -0700611 return out_list
Simon Glass26132882012-01-14 15:12:45 +0000612 for item in alias[lookup_name]:
Simon Glass761648b2022-01-29 14:14:11 -0700613 todo = lookup_email(item, alias, warn_on_error, level + 1)
Simon Glass26132882012-01-14 15:12:45 +0000614 for new_item in todo:
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500615 if new_item not in out_list:
Simon Glass26132882012-01-14 15:12:45 +0000616 out_list.append(new_item)
617
Simon Glass26132882012-01-14 15:12:45 +0000618 return out_list
619
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500620
Simon Glass761648b2022-01-29 14:14:11 -0700621def get_top_level():
Simon Glass26132882012-01-14 15:12:45 +0000622 """Return name of top-level directory for this git repo.
623
624 Returns:
Simon Glass7ade94e2025-03-16 08:00:18 +0000625 str: Full path to git top-level directory
Simon Glass26132882012-01-14 15:12:45 +0000626
627 This test makes sure that we are running tests in the right subdir
628
Doug Anderson51d73212012-11-26 15:21:40 +0000629 >>> os.path.realpath(os.path.dirname(__file__)) == \
Simon Glass761648b2022-01-29 14:14:11 -0700630 os.path.join(get_top_level(), 'tools', 'patman')
Simon Glass26132882012-01-14 15:12:45 +0000631 True
632 """
Simon Glass840be732022-01-29 14:14:05 -0700633 return command.output_one_line('git', 'rev-parse', '--show-toplevel')
Simon Glass26132882012-01-14 15:12:45 +0000634
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500635
Simon Glass761648b2022-01-29 14:14:11 -0700636def get_alias_file():
Simon Glass26132882012-01-14 15:12:45 +0000637 """Gets the name of the git alias file.
638
639 Returns:
Simon Glass7ade94e2025-03-16 08:00:18 +0000640 str: Filename of git alias file, or None if none
Simon Glass26132882012-01-14 15:12:45 +0000641 """
Simon Glass840be732022-01-29 14:14:05 -0700642 fname = command.output_one_line('git', 'config', 'sendemail.aliasesfile',
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500643 raise_on_error=False)
Brian Norris94c775d2022-01-07 15:15:55 -0800644 if not fname:
645 return None
646
647 fname = os.path.expanduser(fname.strip())
648 if os.path.isabs(fname):
649 return fname
650
Simon Glass761648b2022-01-29 14:14:11 -0700651 return os.path.join(get_top_level(), fname)
Simon Glass26132882012-01-14 15:12:45 +0000652
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500653
Simon Glass761648b2022-01-29 14:14:11 -0700654def get_default_user_name():
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000655 """Gets the user.name from .gitconfig file.
656
657 Returns:
658 User name found in .gitconfig file, or None if none
659 """
Simon Glass7ade94e2025-03-16 08:00:18 +0000660 uname = command.output_one_line('git', 'config', '--global', '--includes',
661 'user.name')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000662 return uname
663
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500664
Simon Glass761648b2022-01-29 14:14:11 -0700665def get_default_user_email():
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000666 """Gets the user.email from the global .gitconfig file.
667
668 Returns:
669 User's email found in .gitconfig file, or None if none
670 """
Simon Glass7ade94e2025-03-16 08:00:18 +0000671 uemail = command.output_one_line('git', 'config', '--global', '--includes',
672 'user.email')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000673 return uemail
674
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500675
Simon Glass761648b2022-01-29 14:14:11 -0700676def get_default_subject_prefix():
Wu, Josh9873b912015-04-15 10:25:18 +0800677 """Gets the format.subjectprefix from local .git/config file.
678
679 Returns:
680 Subject prefix found in local .git/config file, or None if none
681 """
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500682 sub_prefix = command.output_one_line(
683 'git', 'config', 'format.subjectprefix', raise_on_error=False)
Wu, Josh9873b912015-04-15 10:25:18 +0800684
685 return sub_prefix
686
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500687
Simon Glass761648b2022-01-29 14:14:11 -0700688def setup():
Simon Glass7ade94e2025-03-16 08:00:18 +0000689 """setup() - Set up git utils, by reading the alias files."""
Simon Glass26132882012-01-14 15:12:45 +0000690 # Check for a git alias file also
Simon Glass7ade94e2025-03-16 08:00:18 +0000691 global USE_NO_DECORATE
Simon Glass81bcca82014-08-28 09:43:45 -0600692
Simon Glass761648b2022-01-29 14:14:11 -0700693 cmd = log_cmd(None, count=0)
Simon Glass7ade94e2025-03-16 08:00:18 +0000694 USE_NO_DECORATE = (command.run_one(*cmd, raise_on_error=False)
Simon Glass6af913d2014-08-09 15:33:11 -0600695 .return_code == 0)
Simon Glass26132882012-01-14 15:12:45 +0000696
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500697
Simon Glass414f1e02025-02-27 12:27:30 -0700698def get_hash(spec):
699 """Get the hash of a commit
700
701 Args:
702 spec (str): Git commit to show, e.g. 'my-branch~12'
703
704 Returns:
705 str: Hash of commit
706 """
707 return command.output_one_line('git', 'show', '-s', '--pretty=format:%H',
708 spec)
709
710
Simon Glass761648b2022-01-29 14:14:11 -0700711def get_head():
Simon Glass11aba512012-12-15 10:42:07 +0000712 """Get the hash of the current HEAD
713
714 Returns:
715 Hash of HEAD
716 """
Simon Glass414f1e02025-02-27 12:27:30 -0700717 return get_hash('HEAD')
718
719
720def get_branch():
721 """Get the branch we are currently on
722
723 Return:
724 str: branch name, or None if none
725 """
726 out = command.output_one_line('git', 'rev-parse', '--abbrev-ref', 'HEAD')
727 if out == 'HEAD':
728 return None
729 return out
Simon Glass11aba512012-12-15 10:42:07 +0000730
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500731
Simon Glass26132882012-01-14 15:12:45 +0000732if __name__ == "__main__":
733 import doctest
734
735 doctest.testmod()