blob: f89576ef3b90590878cc3461ac704acf12529715 [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 Glass1bc26b22025-05-08 04:22:18 +020050def count_commits_to_branch(branch, git_dir=None, end=None):
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 Glass1bc26b22025-05-08 04:22:18 +020058 git_dir (str): Path to git repository (None to use default)
59 end (str): End commit to stop before
Simon Glass2eb4da72020-07-05 21:41:51 -060060
Simon Glass26132882012-01-14 15:12:45 +000061 Return:
62 Number of patches that exist on top of the branch
63 """
Simon Glass1bc26b22025-05-08 04:22:18 +020064 if end:
65 rev_range = f'{end}..{branch}'
66 elif branch:
67 us, msg = get_upstream(git_dir or '.git', branch)
68 if not us:
69 raise ValueError(msg)
Simon Glass7ade94e2025-03-16 08:00:18 +000070 rev_range = f'{us}..{branch}'
Simon Glass2eb4da72020-07-05 21:41:51 -060071 else:
72 rev_range = '@{upstream}..'
Simon Glass1bc26b22025-05-08 04:22:18 +020073 cmd = log_cmd(rev_range, git_dir=git_dir, oneline=True)
Simon Glass51f55182025-02-03 09:26:45 -070074 result = command.run_one(*cmd, capture=True, capture_stderr=True,
75 oneline=True, raise_on_error=False)
Simon Glass1c1f2072020-10-29 21:46:34 -060076 if result.return_code:
Simon Glass7ade94e2025-03-16 08:00:18 +000077 raise ValueError(
78 f'Failed to determine upstream: {result.stderr.strip()}')
Simon Glass1c1f2072020-10-29 21:46:34 -060079 patch_count = len(result.stdout.splitlines())
Simon Glass26132882012-01-14 15:12:45 +000080 return patch_count
81
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -050082
Simon Glass761648b2022-01-29 14:14:11 -070083def name_revision(commit_hash):
Simon Glassf204ab12014-12-01 17:33:54 -070084 """Gets the revision name for a commit
85
86 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +000087 commit_hash (str): Commit hash to look up
Simon Glassf204ab12014-12-01 17:33:54 -070088
89 Return:
90 Name of revision, if any, else None
91 """
Simon Glass51f55182025-02-03 09:26:45 -070092 stdout = command.output_one_line('git', 'name-rev', commit_hash)
Simon Glassf204ab12014-12-01 17:33:54 -070093
94 # We expect a commit, a space, then a revision name
95 name = stdout.split(' ')[1].strip()
96 return name
97
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -050098
Simon Glass761648b2022-01-29 14:14:11 -070099def guess_upstream(git_dir, branch):
Simon Glassf204ab12014-12-01 17:33:54 -0700100 """Tries to guess the upstream for a branch
101
102 This lists out top commits on a branch and tries to find a suitable
103 upstream. It does this by looking for the first commit where
104 'git name-rev' returns a plain branch name, with no ! or ^ modifiers.
105
106 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000107 git_dir (str): Git directory containing repo
108 branch (str): Name of branch
Simon Glassf204ab12014-12-01 17:33:54 -0700109
110 Returns:
111 Tuple:
112 Name of upstream branch (e.g. 'upstream/master') or None if none
113 Warning/error message, or None if none
114 """
Simon Glass51f55182025-02-03 09:26:45 -0700115 cmd = log_cmd(branch, git_dir=git_dir, oneline=True, count=100)
116 result = command.run_one(*cmd, capture=True, capture_stderr=True,
117 raise_on_error=False)
Simon Glassf204ab12014-12-01 17:33:54 -0700118 if result.return_code:
Simon Glass7ade94e2025-03-16 08:00:18 +0000119 return None, f"Branch '{branch}' not found"
Simon Glassf204ab12014-12-01 17:33:54 -0700120 for line in result.stdout.splitlines()[1:]:
121 commit_hash = line.split(' ')[0]
Simon Glass761648b2022-01-29 14:14:11 -0700122 name = name_revision(commit_hash)
Simon Glassf204ab12014-12-01 17:33:54 -0700123 if '~' not in name and '^' not in name:
124 if name.startswith('remotes/'):
125 name = name[8:]
Simon Glass7ade94e2025-03-16 08:00:18 +0000126 return name, f"Guessing upstream as '{name}'"
127 return None, f"Cannot find a suitable upstream for branch '{branch}'"
Simon Glassf204ab12014-12-01 17:33:54 -0700128
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500129
Simon Glass761648b2022-01-29 14:14:11 -0700130def get_upstream(git_dir, branch):
Simon Glass11aba512012-12-15 10:42:07 +0000131 """Returns the name of the upstream for a branch
132
133 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000134 git_dir (str): Git directory containing repo
135 branch (str): Name of branch
Simon Glass11aba512012-12-15 10:42:07 +0000136
137 Returns:
Simon Glassf204ab12014-12-01 17:33:54 -0700138 Tuple:
139 Name of upstream branch (e.g. 'upstream/master') or None if none
140 Warning/error message, or None if none
Simon Glass11aba512012-12-15 10:42:07 +0000141 """
Simon Glassd2e95382013-05-08 08:06:08 +0000142 try:
Simon Glass840be732022-01-29 14:14:05 -0700143 remote = command.output_one_line('git', '--git-dir', git_dir, 'config',
Simon Glass7ade94e2025-03-16 08:00:18 +0000144 f'branch.{branch}.remote')
Simon Glass840be732022-01-29 14:14:05 -0700145 merge = command.output_one_line('git', '--git-dir', git_dir, 'config',
Simon Glass7ade94e2025-03-16 08:00:18 +0000146 f'branch.{branch}.merge')
Simon Glass82327692025-02-03 09:26:43 -0700147 except command.CommandExc:
Simon Glass761648b2022-01-29 14:14:11 -0700148 upstream, msg = guess_upstream(git_dir, branch)
Simon Glassf204ab12014-12-01 17:33:54 -0700149 return upstream, msg
Simon Glassd2e95382013-05-08 08:06:08 +0000150
Simon Glass11aba512012-12-15 10:42:07 +0000151 if remote == '.':
Simon Glass7e92f5c2015-01-29 11:35:16 -0700152 return merge, None
Simon Glass7ade94e2025-03-16 08:00:18 +0000153 if remote and merge:
Simon Glass978c1fb2023-10-30 10:22:30 -0700154 # Drop the initial refs/heads from merge
155 leaf = merge.split('/', maxsplit=2)[2:]
Simon Glass7ade94e2025-03-16 08:00:18 +0000156 return f'{remote}/{"/".join(leaf)}', None
157 raise ValueError("Cannot determine upstream branch for branch "
158 f"'{branch}' remote='{remote}', merge='{merge}'")
Simon Glass11aba512012-12-15 10:42:07 +0000159
160
Simon Glass761648b2022-01-29 14:14:11 -0700161def get_range_in_branch(git_dir, branch, include_upstream=False):
Simon Glass11aba512012-12-15 10:42:07 +0000162 """Returns an expression for the commits in the given branch.
163
164 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000165 git_dir (str): Directory containing git repo
166 branch (str): Name of branch
167 include_upstream (bool): Include the upstream commit as well
Simon Glass11aba512012-12-15 10:42:07 +0000168 Return:
169 Expression in the form 'upstream..branch' which can be used to
Simon Glassd2e95382013-05-08 08:06:08 +0000170 access the commits. If the branch does not exist, returns None.
Simon Glass11aba512012-12-15 10:42:07 +0000171 """
Simon Glass761648b2022-01-29 14:14:11 -0700172 upstream, msg = get_upstream(git_dir, branch)
Simon Glassd2e95382013-05-08 08:06:08 +0000173 if not upstream:
Simon Glassf204ab12014-12-01 17:33:54 -0700174 return None, msg
Simon Glass7ade94e2025-03-16 08:00:18 +0000175 rstr = f"{upstream}{'~' if include_upstream else ''}..{branch}"
Simon Glassf204ab12014-12-01 17:33:54 -0700176 return rstr, msg
Simon Glass11aba512012-12-15 10:42:07 +0000177
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500178
Simon Glass761648b2022-01-29 14:14:11 -0700179def count_commits_in_range(git_dir, range_expr):
Simon Glass5eeef462014-12-01 17:33:57 -0700180 """Returns the number of commits in the given range.
181
182 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000183 git_dir (str): Directory containing git repo
184 range_expr (str): Range to check
Simon Glass5eeef462014-12-01 17:33:57 -0700185 Return:
Anatolij Gustschinf2bcb322019-10-27 17:55:04 +0100186 Number of patches that exist in the supplied range or None if none
Simon Glass5eeef462014-12-01 17:33:57 -0700187 were found
188 """
Simon Glass51f55182025-02-03 09:26:45 -0700189 cmd = log_cmd(range_expr, git_dir=git_dir, oneline=True)
190 result = command.run_one(*cmd, capture=True, capture_stderr=True,
191 raise_on_error=False)
Simon Glass5eeef462014-12-01 17:33:57 -0700192 if result.return_code:
Simon Glass7ade94e2025-03-16 08:00:18 +0000193 return None, f"Range '{range_expr}' not found or is invalid"
Simon Glass5eeef462014-12-01 17:33:57 -0700194 patch_count = len(result.stdout.splitlines())
195 return patch_count, None
196
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500197
Simon Glass761648b2022-01-29 14:14:11 -0700198def count_commits_in_branch(git_dir, branch, include_upstream=False):
Simon Glass11aba512012-12-15 10:42:07 +0000199 """Returns the number of commits in the given branch.
200
201 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000202 git_dir (str): Directory containing git repo
203 branch (str): Name of branch
204 include_upstream (bool): Include the upstream commit as well
Simon Glass11aba512012-12-15 10:42:07 +0000205 Return:
Simon Glassd2e95382013-05-08 08:06:08 +0000206 Number of patches that exist on top of the branch, or None if the
207 branch does not exist.
Simon Glass11aba512012-12-15 10:42:07 +0000208 """
Simon Glass761648b2022-01-29 14:14:11 -0700209 range_expr, msg = get_range_in_branch(git_dir, branch, include_upstream)
Simon Glassd2e95382013-05-08 08:06:08 +0000210 if not range_expr:
Simon Glassf204ab12014-12-01 17:33:54 -0700211 return None, msg
Simon Glass761648b2022-01-29 14:14:11 -0700212 return count_commits_in_range(git_dir, range_expr)
Simon Glass11aba512012-12-15 10:42:07 +0000213
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500214
Simon Glass761648b2022-01-29 14:14:11 -0700215def count_commits(commit_range):
Simon Glass11aba512012-12-15 10:42:07 +0000216 """Returns the number of commits in the given range.
217
218 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000219 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
Simon Glass11aba512012-12-15 10:42:07 +0000220 Return:
221 Number of patches that exist on top of the branch
222 """
Simon Glass761648b2022-01-29 14:14:11 -0700223 pipe = [log_cmd(commit_range, oneline=True),
Simon Glass11aba512012-12-15 10:42:07 +0000224 ['wc', '-l']]
Simon Glass840be732022-01-29 14:14:05 -0700225 stdout = command.run_pipe(pipe, capture=True, oneline=True).stdout
Simon Glass11aba512012-12-15 10:42:07 +0000226 patch_count = int(stdout)
227 return patch_count
228
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500229
Simon Glass761648b2022-01-29 14:14:11 -0700230def checkout(commit_hash, git_dir=None, work_tree=None, force=False):
Simon Glass11aba512012-12-15 10:42:07 +0000231 """Checkout the selected commit for this build
232
233 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000234 commit_hash (str): Commit hash to check out
235 git_dir (str): Directory containing git repo, or None for current dir
236 work_tree (str): Git worktree to use, or None if none
237 force (bool): True to force the checkout (git checkout -f)
Simon Glass11aba512012-12-15 10:42:07 +0000238 """
239 pipe = ['git']
240 if git_dir:
241 pipe.extend(['--git-dir', git_dir])
242 if work_tree:
243 pipe.extend(['--work-tree', work_tree])
244 pipe.append('checkout')
245 if force:
246 pipe.append('-f')
247 pipe.append(commit_hash)
Simon Glass840be732022-01-29 14:14:05 -0700248 result = command.run_pipe([pipe], capture=True, raise_on_error=False,
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500249 capture_stderr=True)
Simon Glass11aba512012-12-15 10:42:07 +0000250 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000251 raise OSError(f'git checkout ({pipe}): {result.stderr}')
Simon Glass11aba512012-12-15 10:42:07 +0000252
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500253
Simon Glass7ade94e2025-03-16 08:00:18 +0000254def clone(repo, output_dir):
255 """Clone a repo
Simon Glass11aba512012-12-15 10:42:07 +0000256
257 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000258 repo (str): Repo to clone (e.g. web address)
259 output_dir (str): Directory to close into
Simon Glass11aba512012-12-15 10:42:07 +0000260 """
Simon Glass7ade94e2025-03-16 08:00:18 +0000261 result = command.run_one('git', 'clone', repo, '.', capture=True,
Simon Glass51f55182025-02-03 09:26:45 -0700262 cwd=output_dir, capture_stderr=True)
Simon Glass11aba512012-12-15 10:42:07 +0000263 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000264 raise OSError(f'git clone: {result.stderr}')
Simon Glass11aba512012-12-15 10:42:07 +0000265
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500266
Simon Glass761648b2022-01-29 14:14:11 -0700267def fetch(git_dir=None, work_tree=None):
Simon Glass11aba512012-12-15 10:42:07 +0000268 """Fetch from the origin repo
269
270 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000271 git_dir (str): Directory containing git repo, or None for current dir
272 work_tree (str or None): Git worktree to use, or None if none
Simon Glass11aba512012-12-15 10:42:07 +0000273 """
Simon Glass51f55182025-02-03 09:26:45 -0700274 cmd = ['git']
Simon Glass11aba512012-12-15 10:42:07 +0000275 if git_dir:
Simon Glass51f55182025-02-03 09:26:45 -0700276 cmd.extend(['--git-dir', git_dir])
Simon Glass11aba512012-12-15 10:42:07 +0000277 if work_tree:
Simon Glass51f55182025-02-03 09:26:45 -0700278 cmd.extend(['--work-tree', work_tree])
279 cmd.append('fetch')
280 result = command.run_one(*cmd, capture=True, capture_stderr=True)
Simon Glass11aba512012-12-15 10:42:07 +0000281 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000282 raise OSError(f'git fetch: {result.stderr}')
Simon Glass11aba512012-12-15 10:42:07 +0000283
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500284
Simon Glass761648b2022-01-29 14:14:11 -0700285def check_worktree_is_available(git_dir):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300286 """Check if git-worktree functionality is available
287
288 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000289 git_dir (str): The repository to test in
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300290
291 Returns:
292 True if git-worktree commands will work, False otherwise.
293 """
Simon Glass51f55182025-02-03 09:26:45 -0700294 result = command.run_one('git', '--git-dir', git_dir, 'worktree', 'list',
295 capture=True, capture_stderr=True,
296 raise_on_error=False)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300297 return result.return_code == 0
298
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500299
Simon Glass761648b2022-01-29 14:14:11 -0700300def add_worktree(git_dir, output_dir, commit_hash=None):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300301 """Create and checkout a new git worktree for this build
302
303 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000304 git_dir (str): The repository to checkout the worktree from
305 output_dir (str): Path for the new worktree
306 commit_hash (str): Commit hash to checkout
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300307 """
308 # We need to pass --detach to avoid creating a new branch
Simon Glass51f55182025-02-03 09:26:45 -0700309 cmd = ['git', '--git-dir', git_dir, 'worktree', 'add', '.', '--detach']
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300310 if commit_hash:
Simon Glass51f55182025-02-03 09:26:45 -0700311 cmd.append(commit_hash)
312 result = command.run_one(*cmd, capture=True, cwd=output_dir,
313 capture_stderr=True)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300314 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000315 raise OSError(f'git worktree add: {result.stderr}')
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300316
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500317
Simon Glass761648b2022-01-29 14:14:11 -0700318def prune_worktrees(git_dir):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300319 """Remove administrative files for deleted worktrees
320
321 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000322 git_dir (str): The repository whose deleted worktrees should be pruned
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300323 """
Simon Glass51f55182025-02-03 09:26:45 -0700324 result = command.run_one('git', '--git-dir', git_dir, 'worktree', 'prune',
325 capture=True, capture_stderr=True)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300326 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000327 raise OSError(f'git worktree prune: {result.stderr}')
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300328
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500329
Simon Glass1bc26b22025-05-08 04:22:18 +0200330def create_patches(branch, start, count, ignore_binary, series, signoff=True,
331 git_dir=None, cwd=None):
Simon Glass26132882012-01-14 15:12:45 +0000332 """Create a series of patches from the top of the current branch.
333
334 The patch files are written to the current directory using
335 git format-patch.
336
337 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000338 branch (str): Branch to create patches from (None for current branch)
339 start (int): Commit to start from: 0=HEAD, 1=next one, etc.
340 count (int): number of commits to include
341 ignore_binary (bool): Don't generate patches for binary files
342 series (Series): Series object for this series (set of patches)
343 signoff (bool): True to add signoff lines automatically
Simon Glass1bc26b22025-05-08 04:22:18 +0200344 git_dir (str): Path to git repository (None to use default)
345 cwd (str): Path to use for git operations
Simon Glass26132882012-01-14 15:12:45 +0000346 Return:
Simon Glass24725af2020-07-05 21:41:49 -0600347 Filename of cover letter (None if none)
Simon Glass26132882012-01-14 15:12:45 +0000348 List of filenames of patch files
349 """
Simon Glass1bc26b22025-05-08 04:22:18 +0200350 cmd = ['git']
351 if git_dir:
352 cmd += ['--git-dir', git_dir]
353 cmd += ['format-patch', '-M']
Philipp Tomsich858531a2020-11-24 18:14:52 +0100354 if signoff:
355 cmd.append('--signoff')
Bin Menga04f1212020-05-04 00:52:44 -0700356 if ignore_binary:
357 cmd.append('--no-binary')
Simon Glass26132882012-01-14 15:12:45 +0000358 if series.get('cover'):
359 cmd.append('--cover-letter')
360 prefix = series.GetPatchPrefix()
361 if prefix:
Simon Glass7ade94e2025-03-16 08:00:18 +0000362 cmd += [f'--subject-prefix={prefix}']
Simon Glass2eb4da72020-07-05 21:41:51 -0600363 brname = branch or 'HEAD'
Simon Glass7ade94e2025-03-16 08:00:18 +0000364 cmd += [f'{brname}~{start + count}..{brname}~{start}']
Simon Glass26132882012-01-14 15:12:45 +0000365
Simon Glass1bc26b22025-05-08 04:22:18 +0200366 stdout = command.run_list(cmd, cwd=cwd)
Simon Glass26132882012-01-14 15:12:45 +0000367 files = stdout.splitlines()
368
369 # We have an extra file if there is a cover letter
370 if series.get('cover'):
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500371 return files[0], files[1:]
Simon Glass7ade94e2025-03-16 08:00:18 +0000372 return None, files
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500373
Simon Glass26132882012-01-14 15:12:45 +0000374
Simon Glassdea1ddf2025-04-07 22:51:44 +1200375def build_email_list(in_list, alias, tag=None, warn_on_error=True):
Simon Glass26132882012-01-14 15:12:45 +0000376 """Build a list of email addresses based on an input list.
377
378 Takes a list of email addresses and aliases, and turns this into a list
379 of only email address, by resolving any aliases that are present.
380
381 If the tag is given, then each email address is prepended with this
382 tag and a space. If the tag starts with a minus sign (indicating a
383 command line parameter) then the email address is quoted.
384
385 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000386 in_list (list of str): List of aliases/email addresses
Simon Glass7ade94e2025-03-16 08:00:18 +0000387 alias (dict): Alias dictionary:
388 key: alias
389 value: list of aliases or email addresses
Simon Glassdea1ddf2025-04-07 22:51:44 +1200390 tag (str): Text to put before each address
Simon Glass7ade94e2025-03-16 08:00:18 +0000391 warn_on_error (bool): True to raise an error when an alias fails to
392 match, False to just print a message.
Simon Glass26132882012-01-14 15:12:45 +0000393
394 Returns:
395 List of email addresses
396
397 >>> alias = {}
398 >>> alias['fred'] = ['f.bloggs@napier.co.nz']
399 >>> alias['john'] = ['j.bloggs@napier.co.nz']
400 >>> alias['mary'] = ['Mary Poppins <m.poppins@cloud.net>']
401 >>> alias['boys'] = ['fred', ' john']
402 >>> alias['all'] = ['fred ', 'john', ' mary ']
Simon Glassdea1ddf2025-04-07 22:51:44 +1200403 >>> build_email_list(['john', 'mary'], alias, None)
Simon Glass26132882012-01-14 15:12:45 +0000404 ['j.bloggs@napier.co.nz', 'Mary Poppins <m.poppins@cloud.net>']
Simon Glassdea1ddf2025-04-07 22:51:44 +1200405 >>> build_email_list(['john', 'mary'], alias, '--to')
Simon Glass26132882012-01-14 15:12:45 +0000406 ['--to "j.bloggs@napier.co.nz"', \
407'--to "Mary Poppins <m.poppins@cloud.net>"']
Simon Glassdea1ddf2025-04-07 22:51:44 +1200408 >>> build_email_list(['john', 'mary'], alias, 'Cc')
Simon Glass26132882012-01-14 15:12:45 +0000409 ['Cc j.bloggs@napier.co.nz', 'Cc Mary Poppins <m.poppins@cloud.net>']
410 """
Simon Glass26132882012-01-14 15:12:45 +0000411 raw = []
412 for item in in_list:
Simon Glass761648b2022-01-29 14:14:11 -0700413 raw += lookup_email(item, alias, warn_on_error=warn_on_error)
Simon Glass26132882012-01-14 15:12:45 +0000414 result = []
415 for item in raw:
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500416 if item not in result:
Simon Glass26132882012-01-14 15:12:45 +0000417 result.append(item)
418 if tag:
Simon Glassa8ba0792025-05-08 04:38:30 +0200419 return [x for email in result for x in (tag, email)]
Simon Glass26132882012-01-14 15:12:45 +0000420 return result
421
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500422
Simon Glass761648b2022-01-29 14:14:11 -0700423def check_suppress_cc_config():
Nicolas Boichat0da95742020-07-13 10:50:00 +0800424 """Check if sendemail.suppresscc is configured correctly.
425
426 Returns:
Simon Glass7ade94e2025-03-16 08:00:18 +0000427 bool: True if the option is configured correctly, False otherwise.
Nicolas Boichat0da95742020-07-13 10:50:00 +0800428 """
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500429 suppresscc = command.output_one_line(
430 'git', 'config', 'sendemail.suppresscc', raise_on_error=False)
Nicolas Boichat0da95742020-07-13 10:50:00 +0800431
432 # Other settings should be fine.
Simon Glass7ade94e2025-03-16 08:00:18 +0000433 if suppresscc in ('all', 'cccmd'):
Nicolas Boichat0da95742020-07-13 10:50:00 +0800434 col = terminal.Color()
435
Simon Glass7ade94e2025-03-16 08:00:18 +0000436 print(col.build(col.RED, 'error') +
437 f': git config sendemail.suppresscc set to {suppresscc}\n' +
438 ' patman needs --cc-cmd to be run to set the cc list.\n' +
439 ' Please run:\n' +
440 ' git config --unset sendemail.suppresscc\n' +
441 ' Or read the man page:\n' +
442 ' git send-email --help\n' +
443 ' and set an option that runs --cc-cmd\n')
Nicolas Boichat0da95742020-07-13 10:50:00 +0800444 return False
445
446 return True
447
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500448
Simon Glass761648b2022-01-29 14:14:11 -0700449def email_patches(series, cover_fname, args, dry_run, warn_on_error, cc_fname,
Simon Glass5efa3662025-04-07 22:51:45 +1200450 alias, self_only=False, in_reply_to=None, thread=False,
Simon Glass1bc26b22025-05-08 04:22:18 +0200451 smtp_server=None, cwd=None):
Simon Glass26132882012-01-14 15:12:45 +0000452 """Email a patch series.
453
454 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000455 series (Series): Series object containing destination info
456 cover_fname (str or None): filename of cover letter
457 args (list of str): list of filenames of patch files
458 dry_run (bool): Just return the command that would be run
459 warn_on_error (bool): True to print a warning when an alias fails to
460 match, False to ignore it.
461 cc_fname (str): Filename of Cc file for per-commit Cc
Simon Glass5efa3662025-04-07 22:51:45 +1200462 alias (dict): Alias dictionary:
Simon Glass7ade94e2025-03-16 08:00:18 +0000463 key: alias
464 value: list of aliases or email addresses
Simon Glass5efa3662025-04-07 22:51:45 +1200465 self_only (bool): True to just email to yourself as a test
Simon Glass7ade94e2025-03-16 08:00:18 +0000466 in_reply_to (str or None): If set we'll pass this to git as
467 --in-reply-to - should be a message ID that this is in reply to.
468 thread (bool): True to add --thread to git send-email (make
Mateusz Kulikowski80c2ebc2016-01-14 20:37:41 +0100469 all patches reply to cover-letter or first patch in series)
Simon Glass7ade94e2025-03-16 08:00:18 +0000470 smtp_server (str or None): SMTP server to use to send patches
Simon Glass1bc26b22025-05-08 04:22:18 +0200471 cwd (str): Path to use for patch files (None to use current dir)
Simon Glass26132882012-01-14 15:12:45 +0000472
473 Returns:
474 Git command that was/would be run
475
Doug Anderson51d73212012-11-26 15:21:40 +0000476 # For the duration of this doctest pretend that we ran patman with ./patman
477 >>> _old_argv0 = sys.argv[0]
478 >>> sys.argv[0] = './patman'
479
Simon Glass26132882012-01-14 15:12:45 +0000480 >>> alias = {}
481 >>> alias['fred'] = ['f.bloggs@napier.co.nz']
482 >>> alias['john'] = ['j.bloggs@napier.co.nz']
483 >>> alias['mary'] = ['m.poppins@cloud.net']
484 >>> alias['boys'] = ['fred', ' john']
485 >>> alias['all'] = ['fred ', 'john', ' mary ']
486 >>> alias[os.getenv('USER')] = ['this-is-me@me.com']
Simon Glass794c2592020-06-07 06:45:47 -0600487 >>> series = {}
488 >>> series['to'] = ['fred']
489 >>> series['cc'] = ['mary']
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 \
Simon Glass1ee91c12020-11-03 13:54:10 -0700493"m.poppins@cloud.net" --cc-cmd "./patman send --cc-cmd cc-fname" cover p1 p2'
Simon Glass761648b2022-01-29 14:14:11 -0700494 >>> email_patches(series, None, ['p1'], True, True, 'cc-fname', False, \
Simon Glass12ea5f42013-03-26 13:09:42 +0000495 alias)
Simon Glass26132882012-01-14 15:12:45 +0000496 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
Simon Glass1ee91c12020-11-03 13:54:10 -0700497"m.poppins@cloud.net" --cc-cmd "./patman send --cc-cmd cc-fname" p1'
Simon Glass794c2592020-06-07 06:45:47 -0600498 >>> series['cc'] = ['all']
Simon Glass761648b2022-01-29 14:14:11 -0700499 >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
Simon Glass12ea5f42013-03-26 13:09:42 +0000500 True, alias)
Simon Glass26132882012-01-14 15:12:45 +0000501 'git send-email --annotate --to "this-is-me@me.com" --cc-cmd "./patman \
Simon Glass1ee91c12020-11-03 13:54:10 -0700502send --cc-cmd cc-fname" cover p1 p2'
Simon Glass761648b2022-01-29 14:14:11 -0700503 >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
Simon Glass12ea5f42013-03-26 13:09:42 +0000504 False, alias)
Simon Glass26132882012-01-14 15:12:45 +0000505 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
506"f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \
Simon Glass1ee91c12020-11-03 13:54:10 -0700507"m.poppins@cloud.net" --cc-cmd "./patman send --cc-cmd cc-fname" cover p1 p2'
Doug Anderson51d73212012-11-26 15:21:40 +0000508
509 # Restore argv[0] since we clobbered it.
510 >>> sys.argv[0] = _old_argv0
Simon Glass26132882012-01-14 15:12:45 +0000511 """
Simon Glass5efa3662025-04-07 22:51:45 +1200512 to = build_email_list(series.get('to'), alias, '--to', warn_on_error)
Simon Glass26132882012-01-14 15:12:45 +0000513 if not to:
Simon Glass840be732022-01-29 14:14:05 -0700514 git_config_to = command.output('git', 'config', 'sendemail.to',
Simon Glassc55e0562016-07-25 18:59:00 -0600515 raise_on_error=False)
Masahiro Yamadad91f5b92014-07-18 14:23:20 +0900516 if not git_config_to:
Simon Glass23b8a192019-05-14 15:53:36 -0600517 print("No recipient.\n"
518 "Please add something like this to a commit\n"
519 "Series-to: Fred Bloggs <f.blogs@napier.co.nz>\n"
520 "Or do something like this\n"
521 "git config sendemail.to u-boot@lists.denx.de")
Simon Glass7ade94e2025-03-16 08:00:18 +0000522 return None
Simon Glass761648b2022-01-29 14:14:11 -0700523 cc = build_email_list(list(set(series.get('cc')) - set(series.get('to'))),
Simon Glass5efa3662025-04-07 22:51:45 +1200524 alias, '--cc', warn_on_error)
Simon Glass26132882012-01-14 15:12:45 +0000525 if self_only:
Simon Glass5efa3662025-04-07 22:51:45 +1200526 to = build_email_list([os.getenv('USER')], '--to', alias,
Simon Glassdea1ddf2025-04-07 22:51:44 +1200527 warn_on_error)
Simon Glass26132882012-01-14 15:12:45 +0000528 cc = []
529 cmd = ['git', 'send-email', '--annotate']
Simon Glass8137e302018-06-19 09:56:07 -0600530 if smtp_server:
Simon Glass7ade94e2025-03-16 08:00:18 +0000531 cmd.append(f'--smtp-server={smtp_server}')
Doug Anderson06f27ac2013-03-17 10:31:04 +0000532 if in_reply_to:
Simon Glass7ade94e2025-03-16 08:00:18 +0000533 cmd.append(f'--in-reply-to="{in_reply_to}"')
Mateusz Kulikowski80c2ebc2016-01-14 20:37:41 +0100534 if thread:
535 cmd.append('--thread')
Doug Anderson06f27ac2013-03-17 10:31:04 +0000536
Simon Glass26132882012-01-14 15:12:45 +0000537 cmd += to
538 cmd += cc
Simon Glassa8ba0792025-05-08 04:38:30 +0200539 cmd += ['--cc-cmd', f'{sys.argv[0]} send --cc-cmd {cc_fname}']
Simon Glass26132882012-01-14 15:12:45 +0000540 if cover_fname:
541 cmd.append(cover_fname)
542 cmd += args
Simon Glass26132882012-01-14 15:12:45 +0000543 if not dry_run:
Simon Glass1bc26b22025-05-08 04:22:18 +0200544 command.run(*cmd, capture=False, capture_stderr=False, cwd=cwd)
Simon Glassa8ba0792025-05-08 04:38:30 +0200545 cmdstr = ' '.join([f'"{x}"' if ' ' in x and not '"' in x else x
546 for x in cmd])
Simon Glass47e308e2017-05-29 15:31:25 -0600547 return cmdstr
Simon Glass26132882012-01-14 15:12:45 +0000548
549
Simon Glass5efa3662025-04-07 22:51:45 +1200550def lookup_email(lookup_name, alias, warn_on_error=True, level=0):
Simon Glass26132882012-01-14 15:12:45 +0000551 """If an email address is an alias, look it up and return the full name
552
553 TODO: Why not just use git's own alias feature?
554
555 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000556 lookup_name (str): Alias or email address to look up
Simon Glass5efa3662025-04-07 22:51:45 +1200557 alias (dict): Alias dictionary
Simon Glass7ade94e2025-03-16 08:00:18 +0000558 key: alias
559 value: list of aliases or email addresses
560 warn_on_error (bool): True to print a warning when an alias fails to
561 match, False to ignore it.
562 level (int): Depth of alias stack, used to detect recusion/loops
Simon Glass26132882012-01-14 15:12:45 +0000563
564 Returns:
565 tuple:
566 list containing a list of email addresses
567
568 Raises:
569 OSError if a recursive alias reference was found
570 ValueError if an alias was not found
571
572 >>> alias = {}
573 >>> alias['fred'] = ['f.bloggs@napier.co.nz']
574 >>> alias['john'] = ['j.bloggs@napier.co.nz']
575 >>> alias['mary'] = ['m.poppins@cloud.net']
576 >>> alias['boys'] = ['fred', ' john', 'f.bloggs@napier.co.nz']
577 >>> alias['all'] = ['fred ', 'john', ' mary ']
578 >>> alias['loop'] = ['other', 'john', ' mary ']
579 >>> alias['other'] = ['loop', 'john', ' mary ']
Simon Glass761648b2022-01-29 14:14:11 -0700580 >>> lookup_email('mary', alias)
Simon Glass26132882012-01-14 15:12:45 +0000581 ['m.poppins@cloud.net']
Simon Glass761648b2022-01-29 14:14:11 -0700582 >>> lookup_email('arthur.wellesley@howe.ro.uk', alias)
Simon Glass26132882012-01-14 15:12:45 +0000583 ['arthur.wellesley@howe.ro.uk']
Simon Glass761648b2022-01-29 14:14:11 -0700584 >>> lookup_email('boys', alias)
Simon Glass26132882012-01-14 15:12:45 +0000585 ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz']
Simon Glass761648b2022-01-29 14:14:11 -0700586 >>> lookup_email('all', alias)
Simon Glass26132882012-01-14 15:12:45 +0000587 ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz', 'm.poppins@cloud.net']
Simon Glass761648b2022-01-29 14:14:11 -0700588 >>> lookup_email('odd', alias)
Simon Glass1f975b92021-01-23 08:56:15 -0700589 Alias 'odd' not found
590 []
Simon Glass761648b2022-01-29 14:14:11 -0700591 >>> lookup_email('loop', alias)
Simon Glass26132882012-01-14 15:12:45 +0000592 Traceback (most recent call last):
593 ...
594 OSError: Recursive email alias at 'other'
Simon Glass761648b2022-01-29 14:14:11 -0700595 >>> lookup_email('odd', alias, warn_on_error=False)
Simon Glass12ea5f42013-03-26 13:09:42 +0000596 []
597 >>> # In this case the loop part will effectively be ignored.
Simon Glass761648b2022-01-29 14:14:11 -0700598 >>> lookup_email('loop', alias, warn_on_error=False)
Simon Glassb0cd3412014-08-28 09:43:35 -0600599 Recursive email alias at 'other'
600 Recursive email alias at 'john'
601 Recursive email alias at 'mary'
Simon Glass12ea5f42013-03-26 13:09:42 +0000602 ['j.bloggs@napier.co.nz', 'm.poppins@cloud.net']
Simon Glass26132882012-01-14 15:12:45 +0000603 """
Simon Glass26132882012-01-14 15:12:45 +0000604 lookup_name = lookup_name.strip()
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500605 if '@' in lookup_name: # Perhaps a real email address
Simon Glass26132882012-01-14 15:12:45 +0000606 return [lookup_name]
607
608 lookup_name = lookup_name.lower()
Simon Glass12ea5f42013-03-26 13:09:42 +0000609 col = terminal.Color()
Simon Glass26132882012-01-14 15:12:45 +0000610
Simon Glass12ea5f42013-03-26 13:09:42 +0000611 out_list = []
Simon Glass26132882012-01-14 15:12:45 +0000612 if level > 10:
Simon Glass7ade94e2025-03-16 08:00:18 +0000613 msg = f"Recursive email alias at '{lookup_name}'"
Simon Glass1f975b92021-01-23 08:56:15 -0700614 if warn_on_error:
Paul Burtonf14a1312016-09-27 16:03:51 +0100615 raise OSError(msg)
Simon Glass7ade94e2025-03-16 08:00:18 +0000616 print(col.build(col.RED, msg))
617 return out_list
Simon Glass26132882012-01-14 15:12:45 +0000618
Simon Glass26132882012-01-14 15:12:45 +0000619 if lookup_name:
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500620 if lookup_name not in alias:
Simon Glass7ade94e2025-03-16 08:00:18 +0000621 msg = f"Alias '{lookup_name}' not found"
Simon Glass1f975b92021-01-23 08:56:15 -0700622 if warn_on_error:
Simon Glassf45d3742022-01-29 14:14:17 -0700623 print(col.build(col.RED, msg))
Simon Glass1f975b92021-01-23 08:56:15 -0700624 return out_list
Simon Glass26132882012-01-14 15:12:45 +0000625 for item in alias[lookup_name]:
Simon Glass761648b2022-01-29 14:14:11 -0700626 todo = lookup_email(item, alias, warn_on_error, level + 1)
Simon Glass26132882012-01-14 15:12:45 +0000627 for new_item in todo:
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500628 if new_item not in out_list:
Simon Glass26132882012-01-14 15:12:45 +0000629 out_list.append(new_item)
630
Simon Glass26132882012-01-14 15:12:45 +0000631 return out_list
632
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500633
Simon Glass761648b2022-01-29 14:14:11 -0700634def get_top_level():
Simon Glass26132882012-01-14 15:12:45 +0000635 """Return name of top-level directory for this git repo.
636
637 Returns:
Simon Glass7ade94e2025-03-16 08:00:18 +0000638 str: Full path to git top-level directory
Simon Glass26132882012-01-14 15:12:45 +0000639
640 This test makes sure that we are running tests in the right subdir
641
Doug Anderson51d73212012-11-26 15:21:40 +0000642 >>> os.path.realpath(os.path.dirname(__file__)) == \
Simon Glass761648b2022-01-29 14:14:11 -0700643 os.path.join(get_top_level(), 'tools', 'patman')
Simon Glass26132882012-01-14 15:12:45 +0000644 True
645 """
Simon Glass840be732022-01-29 14:14:05 -0700646 return command.output_one_line('git', 'rev-parse', '--show-toplevel')
Simon Glass26132882012-01-14 15:12:45 +0000647
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500648
Simon Glass761648b2022-01-29 14:14:11 -0700649def get_alias_file():
Simon Glass26132882012-01-14 15:12:45 +0000650 """Gets the name of the git alias file.
651
652 Returns:
Simon Glass7ade94e2025-03-16 08:00:18 +0000653 str: Filename of git alias file, or None if none
Simon Glass26132882012-01-14 15:12:45 +0000654 """
Simon Glass840be732022-01-29 14:14:05 -0700655 fname = command.output_one_line('git', 'config', 'sendemail.aliasesfile',
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500656 raise_on_error=False)
Brian Norris94c775d2022-01-07 15:15:55 -0800657 if not fname:
658 return None
659
660 fname = os.path.expanduser(fname.strip())
661 if os.path.isabs(fname):
662 return fname
663
Simon Glass761648b2022-01-29 14:14:11 -0700664 return os.path.join(get_top_level(), fname)
Simon Glass26132882012-01-14 15:12:45 +0000665
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500666
Simon Glass761648b2022-01-29 14:14:11 -0700667def get_default_user_name():
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000668 """Gets the user.name from .gitconfig file.
669
670 Returns:
671 User name found in .gitconfig file, or None if none
672 """
Simon Glass7ade94e2025-03-16 08:00:18 +0000673 uname = command.output_one_line('git', 'config', '--global', '--includes',
674 'user.name')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000675 return uname
676
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500677
Simon Glass761648b2022-01-29 14:14:11 -0700678def get_default_user_email():
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000679 """Gets the user.email from the global .gitconfig file.
680
681 Returns:
682 User's email found in .gitconfig file, or None if none
683 """
Simon Glass7ade94e2025-03-16 08:00:18 +0000684 uemail = command.output_one_line('git', 'config', '--global', '--includes',
685 'user.email')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000686 return uemail
687
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500688
Simon Glass761648b2022-01-29 14:14:11 -0700689def get_default_subject_prefix():
Wu, Josh9873b912015-04-15 10:25:18 +0800690 """Gets the format.subjectprefix from local .git/config file.
691
692 Returns:
693 Subject prefix found in local .git/config file, or None if none
694 """
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500695 sub_prefix = command.output_one_line(
696 'git', 'config', 'format.subjectprefix', raise_on_error=False)
Wu, Josh9873b912015-04-15 10:25:18 +0800697
698 return sub_prefix
699
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500700
Simon Glass761648b2022-01-29 14:14:11 -0700701def setup():
Simon Glass7ade94e2025-03-16 08:00:18 +0000702 """setup() - Set up git utils, by reading the alias files."""
Simon Glass26132882012-01-14 15:12:45 +0000703 # Check for a git alias file also
Simon Glass7ade94e2025-03-16 08:00:18 +0000704 global USE_NO_DECORATE
Simon Glass81bcca82014-08-28 09:43:45 -0600705
Simon Glass761648b2022-01-29 14:14:11 -0700706 cmd = log_cmd(None, count=0)
Simon Glass7ade94e2025-03-16 08:00:18 +0000707 USE_NO_DECORATE = (command.run_one(*cmd, raise_on_error=False)
Simon Glass6af913d2014-08-09 15:33:11 -0600708 .return_code == 0)
Simon Glass26132882012-01-14 15:12:45 +0000709
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500710
Simon Glass1bc26b22025-05-08 04:22:18 +0200711def get_hash(spec, git_dir=None):
Simon Glass414f1e02025-02-27 12:27:30 -0700712 """Get the hash of a commit
713
714 Args:
715 spec (str): Git commit to show, e.g. 'my-branch~12'
716
717 Returns:
718 str: Hash of commit
719 """
Simon Glass1bc26b22025-05-08 04:22:18 +0200720 cmd = ['git']
721 if git_dir:
722 cmd += ['--git-dir', git_dir]
723 cmd += ['show', '-s', '--pretty=format:%H', spec]
724 return command.output_one_line(*cmd)
Simon Glass414f1e02025-02-27 12:27:30 -0700725
726
Simon Glass761648b2022-01-29 14:14:11 -0700727def get_head():
Simon Glass11aba512012-12-15 10:42:07 +0000728 """Get the hash of the current HEAD
729
730 Returns:
731 Hash of HEAD
732 """
Simon Glass414f1e02025-02-27 12:27:30 -0700733 return get_hash('HEAD')
734
735
Simon Glass1bc26b22025-05-08 04:22:18 +0200736def get_branch(git_dir=None):
Simon Glass414f1e02025-02-27 12:27:30 -0700737 """Get the branch we are currently on
738
739 Return:
740 str: branch name, or None if none
Simon Glass1bc26b22025-05-08 04:22:18 +0200741 git_dir (str): Path to git repository (None to use default)
Simon Glass414f1e02025-02-27 12:27:30 -0700742 """
Simon Glass1bc26b22025-05-08 04:22:18 +0200743 cmd = ['git']
744 if git_dir:
745 cmd += ['--git-dir', git_dir]
746 cmd += ['rev-parse', '--abbrev-ref', 'HEAD']
747 out = command.output_one_line(*cmd, raise_on_error=False)
Simon Glass414f1e02025-02-27 12:27:30 -0700748 if out == 'HEAD':
749 return None
750 return out
Simon Glass11aba512012-12-15 10:42:07 +0000751
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500752
Simon Glass26132882012-01-14 15:12:45 +0000753if __name__ == "__main__":
754 import doctest
755
756 doctest.testmod()