blob: cc57e7b7f7392a1095d587a29f86d474bfda7d59 [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,
Simon Glassb26edf22025-05-08 04:30:55 +020016 count=None, decorate=False):
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 Glassb26edf22025-05-08 04:30:55 +020034 if USE_NO_DECORATE and not decorate:
Simon Glass6af913d2014-08-09 15:33:11 -060035 cmd.append('--no-decorate')
Simon Glassb26edf22025-05-08 04:30:55 +020036 if decorate:
37 cmd.append('--decorate')
Simon Glass299b9092014-08-14 21:59:11 -060038 if reverse:
39 cmd.append('--reverse')
Simon Glassb9dbcb42014-08-09 15:33:10 -060040 if count is not None:
Simon Glass7ade94e2025-03-16 08:00:18 +000041 cmd.append(f'-n{count}')
Simon Glassb9dbcb42014-08-09 15:33:10 -060042 if commit_range:
43 cmd.append(commit_range)
Simon Glass642e9a62016-03-12 18:50:31 -070044
45 # Add this in case we have a branch with the same name as a directory.
46 # This avoids messages like this, for example:
47 # fatal: ambiguous argument 'test': both revision and filename
48 cmd.append('--')
Simon Glassb9dbcb42014-08-09 15:33:10 -060049 return cmd
Simon Glass26132882012-01-14 15:12:45 +000050
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -050051
Simon Glass1bc26b22025-05-08 04:22:18 +020052def count_commits_to_branch(branch, git_dir=None, end=None):
Simon Glass26132882012-01-14 15:12:45 +000053 """Returns number of commits between HEAD and the tracking branch.
54
55 This looks back to the tracking branch and works out the number of commits
56 since then.
57
Simon Glass2eb4da72020-07-05 21:41:51 -060058 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +000059 branch (str or None): Branch to count from (None for current branch)
Simon Glass1bc26b22025-05-08 04:22:18 +020060 git_dir (str): Path to git repository (None to use default)
61 end (str): End commit to stop before
Simon Glass2eb4da72020-07-05 21:41:51 -060062
Simon Glass26132882012-01-14 15:12:45 +000063 Return:
64 Number of patches that exist on top of the branch
65 """
Simon Glass1bc26b22025-05-08 04:22:18 +020066 if end:
67 rev_range = f'{end}..{branch}'
68 elif branch:
69 us, msg = get_upstream(git_dir or '.git', branch)
70 if not us:
71 raise ValueError(msg)
Simon Glass7ade94e2025-03-16 08:00:18 +000072 rev_range = f'{us}..{branch}'
Simon Glass2eb4da72020-07-05 21:41:51 -060073 else:
74 rev_range = '@{upstream}..'
Simon Glass1bc26b22025-05-08 04:22:18 +020075 cmd = log_cmd(rev_range, git_dir=git_dir, oneline=True)
Simon Glass51f55182025-02-03 09:26:45 -070076 result = command.run_one(*cmd, capture=True, capture_stderr=True,
77 oneline=True, raise_on_error=False)
Simon Glass1c1f2072020-10-29 21:46:34 -060078 if result.return_code:
Simon Glass7ade94e2025-03-16 08:00:18 +000079 raise ValueError(
80 f'Failed to determine upstream: {result.stderr.strip()}')
Simon Glass1c1f2072020-10-29 21:46:34 -060081 patch_count = len(result.stdout.splitlines())
Simon Glass26132882012-01-14 15:12:45 +000082 return patch_count
83
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -050084
Simon Glass761648b2022-01-29 14:14:11 -070085def name_revision(commit_hash):
Simon Glassf204ab12014-12-01 17:33:54 -070086 """Gets the revision name for a commit
87
88 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +000089 commit_hash (str): Commit hash to look up
Simon Glassf204ab12014-12-01 17:33:54 -070090
91 Return:
92 Name of revision, if any, else None
93 """
Simon Glass51f55182025-02-03 09:26:45 -070094 stdout = command.output_one_line('git', 'name-rev', commit_hash)
Simon Glassb26edf22025-05-08 04:30:55 +020095 if not stdout:
96 return None
Simon Glassf204ab12014-12-01 17:33:54 -070097
98 # We expect a commit, a space, then a revision name
Simon Glassb26edf22025-05-08 04:30:55 +020099 name = stdout.split()[1].strip()
Simon Glassf204ab12014-12-01 17:33:54 -0700100 return name
101
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500102
Simon Glass761648b2022-01-29 14:14:11 -0700103def guess_upstream(git_dir, branch):
Simon Glassf204ab12014-12-01 17:33:54 -0700104 """Tries to guess the upstream for a branch
105
106 This lists out top commits on a branch and tries to find a suitable
107 upstream. It does this by looking for the first commit where
108 'git name-rev' returns a plain branch name, with no ! or ^ modifiers.
109
110 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000111 git_dir (str): Git directory containing repo
112 branch (str): Name of branch
Simon Glassf204ab12014-12-01 17:33:54 -0700113
114 Returns:
115 Tuple:
116 Name of upstream branch (e.g. 'upstream/master') or None if none
117 Warning/error message, or None if none
118 """
Simon Glassb26edf22025-05-08 04:30:55 +0200119 cmd = log_cmd(branch, git_dir=git_dir, oneline=True, count=100,
120 decorate=True)
Simon Glass51f55182025-02-03 09:26:45 -0700121 result = command.run_one(*cmd, capture=True, capture_stderr=True,
122 raise_on_error=False)
Simon Glassf204ab12014-12-01 17:33:54 -0700123 if result.return_code:
Simon Glass7ade94e2025-03-16 08:00:18 +0000124 return None, f"Branch '{branch}' not found"
Simon Glassf204ab12014-12-01 17:33:54 -0700125 for line in result.stdout.splitlines()[1:]:
Simon Glassb26edf22025-05-08 04:30:55 +0200126 parts = line.split(maxsplit=1)
127 if len(parts) >= 2 and parts[1].startswith('('):
128 commit_hash = parts[0]
129 name = name_revision(commit_hash)
130 if '~' not in name and '^' not in name:
131 if name.startswith('remotes/'):
132 name = name[8:]
133 return name, f"Guessing upstream as '{name}'"
Simon Glass7ade94e2025-03-16 08:00:18 +0000134 return None, f"Cannot find a suitable upstream for branch '{branch}'"
Simon Glassf204ab12014-12-01 17:33:54 -0700135
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500136
Simon Glass761648b2022-01-29 14:14:11 -0700137def get_upstream(git_dir, branch):
Simon Glass11aba512012-12-15 10:42:07 +0000138 """Returns the name of the upstream for a branch
139
140 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000141 git_dir (str): Git directory containing repo
142 branch (str): Name of branch
Simon Glass11aba512012-12-15 10:42:07 +0000143
144 Returns:
Simon Glassf204ab12014-12-01 17:33:54 -0700145 Tuple:
146 Name of upstream branch (e.g. 'upstream/master') or None if none
147 Warning/error message, or None if none
Simon Glass11aba512012-12-15 10:42:07 +0000148 """
Simon Glassd2e95382013-05-08 08:06:08 +0000149 try:
Simon Glass840be732022-01-29 14:14:05 -0700150 remote = command.output_one_line('git', '--git-dir', git_dir, 'config',
Simon Glass7ade94e2025-03-16 08:00:18 +0000151 f'branch.{branch}.remote')
Simon Glass840be732022-01-29 14:14:05 -0700152 merge = command.output_one_line('git', '--git-dir', git_dir, 'config',
Simon Glass7ade94e2025-03-16 08:00:18 +0000153 f'branch.{branch}.merge')
Simon Glass82327692025-02-03 09:26:43 -0700154 except command.CommandExc:
Simon Glass761648b2022-01-29 14:14:11 -0700155 upstream, msg = guess_upstream(git_dir, branch)
Simon Glassf204ab12014-12-01 17:33:54 -0700156 return upstream, msg
Simon Glassd2e95382013-05-08 08:06:08 +0000157
Simon Glass11aba512012-12-15 10:42:07 +0000158 if remote == '.':
Simon Glass7e92f5c2015-01-29 11:35:16 -0700159 return merge, None
Simon Glass7ade94e2025-03-16 08:00:18 +0000160 if remote and merge:
Simon Glass978c1fb2023-10-30 10:22:30 -0700161 # Drop the initial refs/heads from merge
162 leaf = merge.split('/', maxsplit=2)[2:]
Simon Glass7ade94e2025-03-16 08:00:18 +0000163 return f'{remote}/{"/".join(leaf)}', None
164 raise ValueError("Cannot determine upstream branch for branch "
165 f"'{branch}' remote='{remote}', merge='{merge}'")
Simon Glass11aba512012-12-15 10:42:07 +0000166
167
Simon Glass761648b2022-01-29 14:14:11 -0700168def get_range_in_branch(git_dir, branch, include_upstream=False):
Simon Glass11aba512012-12-15 10:42:07 +0000169 """Returns an expression for the commits in the given branch.
170
171 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000172 git_dir (str): Directory containing git repo
173 branch (str): Name of branch
174 include_upstream (bool): Include the upstream commit as well
Simon Glass11aba512012-12-15 10:42:07 +0000175 Return:
176 Expression in the form 'upstream..branch' which can be used to
Simon Glassd2e95382013-05-08 08:06:08 +0000177 access the commits. If the branch does not exist, returns None.
Simon Glass11aba512012-12-15 10:42:07 +0000178 """
Simon Glass761648b2022-01-29 14:14:11 -0700179 upstream, msg = get_upstream(git_dir, branch)
Simon Glassd2e95382013-05-08 08:06:08 +0000180 if not upstream:
Simon Glassf204ab12014-12-01 17:33:54 -0700181 return None, msg
Simon Glass7ade94e2025-03-16 08:00:18 +0000182 rstr = f"{upstream}{'~' if include_upstream else ''}..{branch}"
Simon Glassf204ab12014-12-01 17:33:54 -0700183 return rstr, msg
Simon Glass11aba512012-12-15 10:42:07 +0000184
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500185
Simon Glass761648b2022-01-29 14:14:11 -0700186def count_commits_in_range(git_dir, range_expr):
Simon Glass5eeef462014-12-01 17:33:57 -0700187 """Returns the number of commits in the given range.
188
189 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000190 git_dir (str): Directory containing git repo
191 range_expr (str): Range to check
Simon Glass5eeef462014-12-01 17:33:57 -0700192 Return:
Anatolij Gustschinf2bcb322019-10-27 17:55:04 +0100193 Number of patches that exist in the supplied range or None if none
Simon Glass5eeef462014-12-01 17:33:57 -0700194 were found
195 """
Simon Glass51f55182025-02-03 09:26:45 -0700196 cmd = log_cmd(range_expr, git_dir=git_dir, oneline=True)
197 result = command.run_one(*cmd, capture=True, capture_stderr=True,
198 raise_on_error=False)
Simon Glass5eeef462014-12-01 17:33:57 -0700199 if result.return_code:
Simon Glass7ade94e2025-03-16 08:00:18 +0000200 return None, f"Range '{range_expr}' not found or is invalid"
Simon Glass5eeef462014-12-01 17:33:57 -0700201 patch_count = len(result.stdout.splitlines())
202 return patch_count, None
203
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500204
Simon Glass761648b2022-01-29 14:14:11 -0700205def count_commits_in_branch(git_dir, branch, include_upstream=False):
Simon Glass11aba512012-12-15 10:42:07 +0000206 """Returns the number of commits in the given branch.
207
208 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000209 git_dir (str): Directory containing git repo
210 branch (str): Name of branch
211 include_upstream (bool): Include the upstream commit as well
Simon Glass11aba512012-12-15 10:42:07 +0000212 Return:
Simon Glassd2e95382013-05-08 08:06:08 +0000213 Number of patches that exist on top of the branch, or None if the
214 branch does not exist.
Simon Glass11aba512012-12-15 10:42:07 +0000215 """
Simon Glass761648b2022-01-29 14:14:11 -0700216 range_expr, msg = get_range_in_branch(git_dir, branch, include_upstream)
Simon Glassd2e95382013-05-08 08:06:08 +0000217 if not range_expr:
Simon Glassf204ab12014-12-01 17:33:54 -0700218 return None, msg
Simon Glass761648b2022-01-29 14:14:11 -0700219 return count_commits_in_range(git_dir, range_expr)
Simon Glass11aba512012-12-15 10:42:07 +0000220
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500221
Simon Glass761648b2022-01-29 14:14:11 -0700222def count_commits(commit_range):
Simon Glass11aba512012-12-15 10:42:07 +0000223 """Returns the number of commits in the given range.
224
225 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000226 commit_range (str): Range of commits to count (e.g. 'HEAD..base')
Simon Glass11aba512012-12-15 10:42:07 +0000227 Return:
228 Number of patches that exist on top of the branch
229 """
Simon Glass761648b2022-01-29 14:14:11 -0700230 pipe = [log_cmd(commit_range, oneline=True),
Simon Glass11aba512012-12-15 10:42:07 +0000231 ['wc', '-l']]
Simon Glass840be732022-01-29 14:14:05 -0700232 stdout = command.run_pipe(pipe, capture=True, oneline=True).stdout
Simon Glass11aba512012-12-15 10:42:07 +0000233 patch_count = int(stdout)
234 return patch_count
235
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500236
Simon Glass761648b2022-01-29 14:14:11 -0700237def checkout(commit_hash, git_dir=None, work_tree=None, force=False):
Simon Glass11aba512012-12-15 10:42:07 +0000238 """Checkout the selected commit for this build
239
240 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000241 commit_hash (str): Commit hash to check out
242 git_dir (str): Directory containing git repo, or None for current dir
243 work_tree (str): Git worktree to use, or None if none
244 force (bool): True to force the checkout (git checkout -f)
Simon Glass11aba512012-12-15 10:42:07 +0000245 """
246 pipe = ['git']
247 if git_dir:
248 pipe.extend(['--git-dir', git_dir])
249 if work_tree:
250 pipe.extend(['--work-tree', work_tree])
251 pipe.append('checkout')
252 if force:
253 pipe.append('-f')
254 pipe.append(commit_hash)
Simon Glass840be732022-01-29 14:14:05 -0700255 result = command.run_pipe([pipe], capture=True, raise_on_error=False,
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500256 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 checkout ({pipe}): {result.stderr}')
Simon Glass11aba512012-12-15 10:42:07 +0000259
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500260
Simon Glass7ade94e2025-03-16 08:00:18 +0000261def clone(repo, output_dir):
262 """Clone a repo
Simon Glass11aba512012-12-15 10:42:07 +0000263
264 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000265 repo (str): Repo to clone (e.g. web address)
266 output_dir (str): Directory to close into
Simon Glass11aba512012-12-15 10:42:07 +0000267 """
Simon Glass7ade94e2025-03-16 08:00:18 +0000268 result = command.run_one('git', 'clone', repo, '.', capture=True,
Simon Glass51f55182025-02-03 09:26:45 -0700269 cwd=output_dir, capture_stderr=True)
Simon Glass11aba512012-12-15 10:42:07 +0000270 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000271 raise OSError(f'git clone: {result.stderr}')
Simon Glass11aba512012-12-15 10:42:07 +0000272
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500273
Simon Glass761648b2022-01-29 14:14:11 -0700274def fetch(git_dir=None, work_tree=None):
Simon Glass11aba512012-12-15 10:42:07 +0000275 """Fetch from the origin repo
276
277 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000278 git_dir (str): Directory containing git repo, or None for current dir
279 work_tree (str or None): Git worktree to use, or None if none
Simon Glass11aba512012-12-15 10:42:07 +0000280 """
Simon Glass51f55182025-02-03 09:26:45 -0700281 cmd = ['git']
Simon Glass11aba512012-12-15 10:42:07 +0000282 if git_dir:
Simon Glass51f55182025-02-03 09:26:45 -0700283 cmd.extend(['--git-dir', git_dir])
Simon Glass11aba512012-12-15 10:42:07 +0000284 if work_tree:
Simon Glass51f55182025-02-03 09:26:45 -0700285 cmd.extend(['--work-tree', work_tree])
286 cmd.append('fetch')
287 result = command.run_one(*cmd, capture=True, capture_stderr=True)
Simon Glass11aba512012-12-15 10:42:07 +0000288 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000289 raise OSError(f'git fetch: {result.stderr}')
Simon Glass11aba512012-12-15 10:42:07 +0000290
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500291
Simon Glass761648b2022-01-29 14:14:11 -0700292def check_worktree_is_available(git_dir):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300293 """Check if git-worktree functionality is available
294
295 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000296 git_dir (str): The repository to test in
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300297
298 Returns:
299 True if git-worktree commands will work, False otherwise.
300 """
Simon Glass51f55182025-02-03 09:26:45 -0700301 result = command.run_one('git', '--git-dir', git_dir, 'worktree', 'list',
302 capture=True, capture_stderr=True,
303 raise_on_error=False)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300304 return result.return_code == 0
305
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500306
Simon Glass761648b2022-01-29 14:14:11 -0700307def add_worktree(git_dir, output_dir, commit_hash=None):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300308 """Create and checkout a new git worktree for this build
309
310 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000311 git_dir (str): The repository to checkout the worktree from
312 output_dir (str): Path for the new worktree
313 commit_hash (str): Commit hash to checkout
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300314 """
315 # We need to pass --detach to avoid creating a new branch
Simon Glass51f55182025-02-03 09:26:45 -0700316 cmd = ['git', '--git-dir', git_dir, 'worktree', 'add', '.', '--detach']
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300317 if commit_hash:
Simon Glass51f55182025-02-03 09:26:45 -0700318 cmd.append(commit_hash)
319 result = command.run_one(*cmd, capture=True, cwd=output_dir,
320 capture_stderr=True)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300321 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000322 raise OSError(f'git worktree add: {result.stderr}')
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300323
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500324
Simon Glass761648b2022-01-29 14:14:11 -0700325def prune_worktrees(git_dir):
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300326 """Remove administrative files for deleted worktrees
327
328 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000329 git_dir (str): The repository whose deleted worktrees should be pruned
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300330 """
Simon Glass51f55182025-02-03 09:26:45 -0700331 result = command.run_one('git', '--git-dir', git_dir, 'worktree', 'prune',
332 capture=True, capture_stderr=True)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300333 if result.return_code != 0:
Simon Glass7ade94e2025-03-16 08:00:18 +0000334 raise OSError(f'git worktree prune: {result.stderr}')
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300335
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500336
Simon Glass1bc26b22025-05-08 04:22:18 +0200337def create_patches(branch, start, count, ignore_binary, series, signoff=True,
338 git_dir=None, cwd=None):
Simon Glass26132882012-01-14 15:12:45 +0000339 """Create a series of patches from the top of the current branch.
340
341 The patch files are written to the current directory using
342 git format-patch.
343
344 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000345 branch (str): Branch to create patches from (None for current branch)
346 start (int): Commit to start from: 0=HEAD, 1=next one, etc.
347 count (int): number of commits to include
348 ignore_binary (bool): Don't generate patches for binary files
349 series (Series): Series object for this series (set of patches)
350 signoff (bool): True to add signoff lines automatically
Simon Glass1bc26b22025-05-08 04:22:18 +0200351 git_dir (str): Path to git repository (None to use default)
352 cwd (str): Path to use for git operations
Simon Glass26132882012-01-14 15:12:45 +0000353 Return:
Simon Glass24725af2020-07-05 21:41:49 -0600354 Filename of cover letter (None if none)
Simon Glass26132882012-01-14 15:12:45 +0000355 List of filenames of patch files
356 """
Simon Glass1bc26b22025-05-08 04:22:18 +0200357 cmd = ['git']
358 if git_dir:
359 cmd += ['--git-dir', git_dir]
360 cmd += ['format-patch', '-M']
Philipp Tomsich858531a2020-11-24 18:14:52 +0100361 if signoff:
362 cmd.append('--signoff')
Bin Menga04f1212020-05-04 00:52:44 -0700363 if ignore_binary:
364 cmd.append('--no-binary')
Simon Glass26132882012-01-14 15:12:45 +0000365 if series.get('cover'):
366 cmd.append('--cover-letter')
367 prefix = series.GetPatchPrefix()
368 if prefix:
Simon Glass7ade94e2025-03-16 08:00:18 +0000369 cmd += [f'--subject-prefix={prefix}']
Simon Glass2eb4da72020-07-05 21:41:51 -0600370 brname = branch or 'HEAD'
Simon Glass7ade94e2025-03-16 08:00:18 +0000371 cmd += [f'{brname}~{start + count}..{brname}~{start}']
Simon Glass26132882012-01-14 15:12:45 +0000372
Simon Glass1bc26b22025-05-08 04:22:18 +0200373 stdout = command.run_list(cmd, cwd=cwd)
Simon Glass26132882012-01-14 15:12:45 +0000374 files = stdout.splitlines()
375
376 # We have an extra file if there is a cover letter
377 if series.get('cover'):
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500378 return files[0], files[1:]
Simon Glass7ade94e2025-03-16 08:00:18 +0000379 return None, files
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500380
Simon Glass26132882012-01-14 15:12:45 +0000381
Simon Glassdea1ddf2025-04-07 22:51:44 +1200382def build_email_list(in_list, alias, tag=None, warn_on_error=True):
Simon Glass26132882012-01-14 15:12:45 +0000383 """Build a list of email addresses based on an input list.
384
385 Takes a list of email addresses and aliases, and turns this into a list
386 of only email address, by resolving any aliases that are present.
387
388 If the tag is given, then each email address is prepended with this
389 tag and a space. If the tag starts with a minus sign (indicating a
390 command line parameter) then the email address is quoted.
391
392 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000393 in_list (list of str): List of aliases/email addresses
Simon Glass7ade94e2025-03-16 08:00:18 +0000394 alias (dict): Alias dictionary:
395 key: alias
396 value: list of aliases or email addresses
Simon Glassdea1ddf2025-04-07 22:51:44 +1200397 tag (str): Text to put before each address
Simon Glass7ade94e2025-03-16 08:00:18 +0000398 warn_on_error (bool): True to raise an error when an alias fails to
399 match, False to just print a message.
Simon Glass26132882012-01-14 15:12:45 +0000400
401 Returns:
402 List of email addresses
403
404 >>> alias = {}
405 >>> alias['fred'] = ['f.bloggs@napier.co.nz']
406 >>> alias['john'] = ['j.bloggs@napier.co.nz']
407 >>> alias['mary'] = ['Mary Poppins <m.poppins@cloud.net>']
408 >>> alias['boys'] = ['fred', ' john']
409 >>> alias['all'] = ['fred ', 'john', ' mary ']
Simon Glassdea1ddf2025-04-07 22:51:44 +1200410 >>> build_email_list(['john', 'mary'], alias, None)
Simon Glass26132882012-01-14 15:12:45 +0000411 ['j.bloggs@napier.co.nz', 'Mary Poppins <m.poppins@cloud.net>']
Simon Glassdea1ddf2025-04-07 22:51:44 +1200412 >>> build_email_list(['john', 'mary'], alias, '--to')
Simon Glass26132882012-01-14 15:12:45 +0000413 ['--to "j.bloggs@napier.co.nz"', \
414'--to "Mary Poppins <m.poppins@cloud.net>"']
Simon Glassdea1ddf2025-04-07 22:51:44 +1200415 >>> build_email_list(['john', 'mary'], alias, 'Cc')
Simon Glass26132882012-01-14 15:12:45 +0000416 ['Cc j.bloggs@napier.co.nz', 'Cc Mary Poppins <m.poppins@cloud.net>']
417 """
Simon Glass26132882012-01-14 15:12:45 +0000418 raw = []
419 for item in in_list:
Simon Glass761648b2022-01-29 14:14:11 -0700420 raw += lookup_email(item, alias, warn_on_error=warn_on_error)
Simon Glass26132882012-01-14 15:12:45 +0000421 result = []
422 for item in raw:
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500423 if item not in result:
Simon Glass26132882012-01-14 15:12:45 +0000424 result.append(item)
425 if tag:
Simon Glassa8ba0792025-05-08 04:38:30 +0200426 return [x for email in result for x in (tag, email)]
Simon Glass26132882012-01-14 15:12:45 +0000427 return result
428
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500429
Simon Glass761648b2022-01-29 14:14:11 -0700430def check_suppress_cc_config():
Nicolas Boichat0da95742020-07-13 10:50:00 +0800431 """Check if sendemail.suppresscc is configured correctly.
432
433 Returns:
Simon Glass7ade94e2025-03-16 08:00:18 +0000434 bool: True if the option is configured correctly, False otherwise.
Nicolas Boichat0da95742020-07-13 10:50:00 +0800435 """
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500436 suppresscc = command.output_one_line(
437 'git', 'config', 'sendemail.suppresscc', raise_on_error=False)
Nicolas Boichat0da95742020-07-13 10:50:00 +0800438
439 # Other settings should be fine.
Simon Glass7ade94e2025-03-16 08:00:18 +0000440 if suppresscc in ('all', 'cccmd'):
Nicolas Boichat0da95742020-07-13 10:50:00 +0800441 col = terminal.Color()
442
Simon Glass7ade94e2025-03-16 08:00:18 +0000443 print(col.build(col.RED, 'error') +
444 f': git config sendemail.suppresscc set to {suppresscc}\n' +
445 ' patman needs --cc-cmd to be run to set the cc list.\n' +
446 ' Please run:\n' +
447 ' git config --unset sendemail.suppresscc\n' +
448 ' Or read the man page:\n' +
449 ' git send-email --help\n' +
450 ' and set an option that runs --cc-cmd\n')
Nicolas Boichat0da95742020-07-13 10:50:00 +0800451 return False
452
453 return True
454
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500455
Simon Glass761648b2022-01-29 14:14:11 -0700456def email_patches(series, cover_fname, args, dry_run, warn_on_error, cc_fname,
Simon Glass5efa3662025-04-07 22:51:45 +1200457 alias, self_only=False, in_reply_to=None, thread=False,
Simon Glass1bc26b22025-05-08 04:22:18 +0200458 smtp_server=None, cwd=None):
Simon Glass26132882012-01-14 15:12:45 +0000459 """Email a patch series.
460
461 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000462 series (Series): Series object containing destination info
463 cover_fname (str or None): filename of cover letter
464 args (list of str): list of filenames of patch files
465 dry_run (bool): Just return the command that would be run
466 warn_on_error (bool): True to print a warning when an alias fails to
467 match, False to ignore it.
468 cc_fname (str): Filename of Cc file for per-commit Cc
Simon Glass5efa3662025-04-07 22:51:45 +1200469 alias (dict): Alias dictionary:
Simon Glass7ade94e2025-03-16 08:00:18 +0000470 key: alias
471 value: list of aliases or email addresses
Simon Glass5efa3662025-04-07 22:51:45 +1200472 self_only (bool): True to just email to yourself as a test
Simon Glass7ade94e2025-03-16 08:00:18 +0000473 in_reply_to (str or None): If set we'll pass this to git as
474 --in-reply-to - should be a message ID that this is in reply to.
475 thread (bool): True to add --thread to git send-email (make
Mateusz Kulikowski80c2ebc2016-01-14 20:37:41 +0100476 all patches reply to cover-letter or first patch in series)
Simon Glass7ade94e2025-03-16 08:00:18 +0000477 smtp_server (str or None): SMTP server to use to send patches
Simon Glass1bc26b22025-05-08 04:22:18 +0200478 cwd (str): Path to use for patch files (None to use current dir)
Simon Glass26132882012-01-14 15:12:45 +0000479
480 Returns:
481 Git command that was/would be run
482
Doug Anderson51d73212012-11-26 15:21:40 +0000483 # For the duration of this doctest pretend that we ran patman with ./patman
484 >>> _old_argv0 = sys.argv[0]
485 >>> sys.argv[0] = './patman'
486
Simon Glass26132882012-01-14 15:12:45 +0000487 >>> alias = {}
488 >>> alias['fred'] = ['f.bloggs@napier.co.nz']
489 >>> alias['john'] = ['j.bloggs@napier.co.nz']
490 >>> alias['mary'] = ['m.poppins@cloud.net']
491 >>> alias['boys'] = ['fred', ' john']
492 >>> alias['all'] = ['fred ', 'john', ' mary ']
493 >>> alias[os.getenv('USER')] = ['this-is-me@me.com']
Simon Glass794c2592020-06-07 06:45:47 -0600494 >>> series = {}
495 >>> series['to'] = ['fred']
496 >>> series['cc'] = ['mary']
Simon Glass761648b2022-01-29 14:14:11 -0700497 >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
Simon Glass12ea5f42013-03-26 13:09:42 +0000498 False, alias)
Simon Glass26132882012-01-14 15:12:45 +0000499 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
Simon Glass1ee91c12020-11-03 13:54:10 -0700500"m.poppins@cloud.net" --cc-cmd "./patman send --cc-cmd cc-fname" cover p1 p2'
Simon Glass761648b2022-01-29 14:14:11 -0700501 >>> email_patches(series, None, ['p1'], True, True, 'cc-fname', False, \
Simon Glass12ea5f42013-03-26 13:09:42 +0000502 alias)
Simon Glass26132882012-01-14 15:12:45 +0000503 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
Simon Glass1ee91c12020-11-03 13:54:10 -0700504"m.poppins@cloud.net" --cc-cmd "./patman send --cc-cmd cc-fname" p1'
Simon Glass794c2592020-06-07 06:45:47 -0600505 >>> series['cc'] = ['all']
Simon Glass761648b2022-01-29 14:14:11 -0700506 >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
Simon Glass12ea5f42013-03-26 13:09:42 +0000507 True, alias)
Simon Glass26132882012-01-14 15:12:45 +0000508 'git send-email --annotate --to "this-is-me@me.com" --cc-cmd "./patman \
Simon Glass1ee91c12020-11-03 13:54:10 -0700509send --cc-cmd cc-fname" cover p1 p2'
Simon Glass761648b2022-01-29 14:14:11 -0700510 >>> email_patches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
Simon Glass12ea5f42013-03-26 13:09:42 +0000511 False, alias)
Simon Glass26132882012-01-14 15:12:45 +0000512 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
513"f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \
Simon Glass1ee91c12020-11-03 13:54:10 -0700514"m.poppins@cloud.net" --cc-cmd "./patman send --cc-cmd cc-fname" cover p1 p2'
Doug Anderson51d73212012-11-26 15:21:40 +0000515
516 # Restore argv[0] since we clobbered it.
517 >>> sys.argv[0] = _old_argv0
Simon Glass26132882012-01-14 15:12:45 +0000518 """
Simon Glass5efa3662025-04-07 22:51:45 +1200519 to = build_email_list(series.get('to'), alias, '--to', warn_on_error)
Simon Glass26132882012-01-14 15:12:45 +0000520 if not to:
Simon Glass840be732022-01-29 14:14:05 -0700521 git_config_to = command.output('git', 'config', 'sendemail.to',
Simon Glassc55e0562016-07-25 18:59:00 -0600522 raise_on_error=False)
Masahiro Yamadad91f5b92014-07-18 14:23:20 +0900523 if not git_config_to:
Simon Glass23b8a192019-05-14 15:53:36 -0600524 print("No recipient.\n"
525 "Please add something like this to a commit\n"
526 "Series-to: Fred Bloggs <f.blogs@napier.co.nz>\n"
527 "Or do something like this\n"
528 "git config sendemail.to u-boot@lists.denx.de")
Simon Glass7ade94e2025-03-16 08:00:18 +0000529 return None
Simon Glass761648b2022-01-29 14:14:11 -0700530 cc = build_email_list(list(set(series.get('cc')) - set(series.get('to'))),
Simon Glass5efa3662025-04-07 22:51:45 +1200531 alias, '--cc', warn_on_error)
Simon Glass26132882012-01-14 15:12:45 +0000532 if self_only:
Simon Glass5efa3662025-04-07 22:51:45 +1200533 to = build_email_list([os.getenv('USER')], '--to', alias,
Simon Glassdea1ddf2025-04-07 22:51:44 +1200534 warn_on_error)
Simon Glass26132882012-01-14 15:12:45 +0000535 cc = []
536 cmd = ['git', 'send-email', '--annotate']
Simon Glass8137e302018-06-19 09:56:07 -0600537 if smtp_server:
Simon Glass7ade94e2025-03-16 08:00:18 +0000538 cmd.append(f'--smtp-server={smtp_server}')
Doug Anderson06f27ac2013-03-17 10:31:04 +0000539 if in_reply_to:
Simon Glass7ade94e2025-03-16 08:00:18 +0000540 cmd.append(f'--in-reply-to="{in_reply_to}"')
Mateusz Kulikowski80c2ebc2016-01-14 20:37:41 +0100541 if thread:
542 cmd.append('--thread')
Doug Anderson06f27ac2013-03-17 10:31:04 +0000543
Simon Glass26132882012-01-14 15:12:45 +0000544 cmd += to
545 cmd += cc
Simon Glassa8ba0792025-05-08 04:38:30 +0200546 cmd += ['--cc-cmd', f'{sys.argv[0]} send --cc-cmd {cc_fname}']
Simon Glass26132882012-01-14 15:12:45 +0000547 if cover_fname:
548 cmd.append(cover_fname)
549 cmd += args
Simon Glass26132882012-01-14 15:12:45 +0000550 if not dry_run:
Simon Glass1bc26b22025-05-08 04:22:18 +0200551 command.run(*cmd, capture=False, capture_stderr=False, cwd=cwd)
Simon Glassa8ba0792025-05-08 04:38:30 +0200552 cmdstr = ' '.join([f'"{x}"' if ' ' in x and not '"' in x else x
553 for x in cmd])
Simon Glass47e308e2017-05-29 15:31:25 -0600554 return cmdstr
Simon Glass26132882012-01-14 15:12:45 +0000555
556
Simon Glass5efa3662025-04-07 22:51:45 +1200557def lookup_email(lookup_name, alias, warn_on_error=True, level=0):
Simon Glass26132882012-01-14 15:12:45 +0000558 """If an email address is an alias, look it up and return the full name
559
560 TODO: Why not just use git's own alias feature?
561
562 Args:
Simon Glass7ade94e2025-03-16 08:00:18 +0000563 lookup_name (str): Alias or email address to look up
Simon Glass5efa3662025-04-07 22:51:45 +1200564 alias (dict): Alias dictionary
Simon Glass7ade94e2025-03-16 08:00:18 +0000565 key: alias
566 value: list of aliases or email addresses
567 warn_on_error (bool): True to print a warning when an alias fails to
568 match, False to ignore it.
569 level (int): Depth of alias stack, used to detect recusion/loops
Simon Glass26132882012-01-14 15:12:45 +0000570
571 Returns:
572 tuple:
573 list containing a list of email addresses
574
575 Raises:
576 OSError if a recursive alias reference was found
577 ValueError if an alias was not found
578
579 >>> alias = {}
580 >>> alias['fred'] = ['f.bloggs@napier.co.nz']
581 >>> alias['john'] = ['j.bloggs@napier.co.nz']
582 >>> alias['mary'] = ['m.poppins@cloud.net']
583 >>> alias['boys'] = ['fred', ' john', 'f.bloggs@napier.co.nz']
584 >>> alias['all'] = ['fred ', 'john', ' mary ']
585 >>> alias['loop'] = ['other', 'john', ' mary ']
586 >>> alias['other'] = ['loop', 'john', ' mary ']
Simon Glass761648b2022-01-29 14:14:11 -0700587 >>> lookup_email('mary', alias)
Simon Glass26132882012-01-14 15:12:45 +0000588 ['m.poppins@cloud.net']
Simon Glass761648b2022-01-29 14:14:11 -0700589 >>> lookup_email('arthur.wellesley@howe.ro.uk', alias)
Simon Glass26132882012-01-14 15:12:45 +0000590 ['arthur.wellesley@howe.ro.uk']
Simon Glass761648b2022-01-29 14:14:11 -0700591 >>> lookup_email('boys', alias)
Simon Glass26132882012-01-14 15:12:45 +0000592 ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz']
Simon Glass761648b2022-01-29 14:14:11 -0700593 >>> lookup_email('all', alias)
Simon Glass26132882012-01-14 15:12:45 +0000594 ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz', 'm.poppins@cloud.net']
Simon Glass761648b2022-01-29 14:14:11 -0700595 >>> lookup_email('odd', alias)
Simon Glass1f975b92021-01-23 08:56:15 -0700596 Alias 'odd' not found
597 []
Simon Glass761648b2022-01-29 14:14:11 -0700598 >>> lookup_email('loop', alias)
Simon Glass26132882012-01-14 15:12:45 +0000599 Traceback (most recent call last):
600 ...
601 OSError: Recursive email alias at 'other'
Simon Glass761648b2022-01-29 14:14:11 -0700602 >>> lookup_email('odd', alias, warn_on_error=False)
Simon Glass12ea5f42013-03-26 13:09:42 +0000603 []
604 >>> # In this case the loop part will effectively be ignored.
Simon Glass761648b2022-01-29 14:14:11 -0700605 >>> lookup_email('loop', alias, warn_on_error=False)
Simon Glassb0cd3412014-08-28 09:43:35 -0600606 Recursive email alias at 'other'
607 Recursive email alias at 'john'
608 Recursive email alias at 'mary'
Simon Glass12ea5f42013-03-26 13:09:42 +0000609 ['j.bloggs@napier.co.nz', 'm.poppins@cloud.net']
Simon Glass26132882012-01-14 15:12:45 +0000610 """
Simon Glass26132882012-01-14 15:12:45 +0000611 lookup_name = lookup_name.strip()
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500612 if '@' in lookup_name: # Perhaps a real email address
Simon Glass26132882012-01-14 15:12:45 +0000613 return [lookup_name]
614
615 lookup_name = lookup_name.lower()
Simon Glass12ea5f42013-03-26 13:09:42 +0000616 col = terminal.Color()
Simon Glass26132882012-01-14 15:12:45 +0000617
Simon Glass12ea5f42013-03-26 13:09:42 +0000618 out_list = []
Simon Glass26132882012-01-14 15:12:45 +0000619 if level > 10:
Simon Glass7ade94e2025-03-16 08:00:18 +0000620 msg = f"Recursive email alias at '{lookup_name}'"
Simon Glass1f975b92021-01-23 08:56:15 -0700621 if warn_on_error:
Paul Burtonf14a1312016-09-27 16:03:51 +0100622 raise OSError(msg)
Simon Glass7ade94e2025-03-16 08:00:18 +0000623 print(col.build(col.RED, msg))
624 return out_list
Simon Glass26132882012-01-14 15:12:45 +0000625
Simon Glass26132882012-01-14 15:12:45 +0000626 if lookup_name:
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500627 if lookup_name not in alias:
Simon Glass7ade94e2025-03-16 08:00:18 +0000628 msg = f"Alias '{lookup_name}' not found"
Simon Glass1f975b92021-01-23 08:56:15 -0700629 if warn_on_error:
Simon Glassf45d3742022-01-29 14:14:17 -0700630 print(col.build(col.RED, msg))
Simon Glass1f975b92021-01-23 08:56:15 -0700631 return out_list
Simon Glass26132882012-01-14 15:12:45 +0000632 for item in alias[lookup_name]:
Simon Glass761648b2022-01-29 14:14:11 -0700633 todo = lookup_email(item, alias, warn_on_error, level + 1)
Simon Glass26132882012-01-14 15:12:45 +0000634 for new_item in todo:
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500635 if new_item not in out_list:
Simon Glass26132882012-01-14 15:12:45 +0000636 out_list.append(new_item)
637
Simon Glass26132882012-01-14 15:12:45 +0000638 return out_list
639
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500640
Simon Glass761648b2022-01-29 14:14:11 -0700641def get_top_level():
Simon Glass26132882012-01-14 15:12:45 +0000642 """Return name of top-level directory for this git repo.
643
644 Returns:
Simon Glass7ade94e2025-03-16 08:00:18 +0000645 str: Full path to git top-level directory
Simon Glass26132882012-01-14 15:12:45 +0000646
647 This test makes sure that we are running tests in the right subdir
648
Doug Anderson51d73212012-11-26 15:21:40 +0000649 >>> os.path.realpath(os.path.dirname(__file__)) == \
Simon Glass761648b2022-01-29 14:14:11 -0700650 os.path.join(get_top_level(), 'tools', 'patman')
Simon Glass26132882012-01-14 15:12:45 +0000651 True
652 """
Simon Glass840be732022-01-29 14:14:05 -0700653 return command.output_one_line('git', 'rev-parse', '--show-toplevel')
Simon Glass26132882012-01-14 15:12:45 +0000654
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500655
Simon Glass761648b2022-01-29 14:14:11 -0700656def get_alias_file():
Simon Glass26132882012-01-14 15:12:45 +0000657 """Gets the name of the git alias file.
658
659 Returns:
Simon Glass7ade94e2025-03-16 08:00:18 +0000660 str: Filename of git alias file, or None if none
Simon Glass26132882012-01-14 15:12:45 +0000661 """
Simon Glass840be732022-01-29 14:14:05 -0700662 fname = command.output_one_line('git', 'config', 'sendemail.aliasesfile',
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500663 raise_on_error=False)
Brian Norris94c775d2022-01-07 15:15:55 -0800664 if not fname:
665 return None
666
667 fname = os.path.expanduser(fname.strip())
668 if os.path.isabs(fname):
669 return fname
670
Simon Glass761648b2022-01-29 14:14:11 -0700671 return os.path.join(get_top_level(), fname)
Simon Glass26132882012-01-14 15:12:45 +0000672
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500673
Simon Glass761648b2022-01-29 14:14:11 -0700674def get_default_user_name():
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000675 """Gets the user.name from .gitconfig file.
676
677 Returns:
678 User name found in .gitconfig file, or None if none
679 """
Simon Glass7ade94e2025-03-16 08:00:18 +0000680 uname = command.output_one_line('git', 'config', '--global', '--includes',
681 'user.name')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000682 return uname
683
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500684
Simon Glass761648b2022-01-29 14:14:11 -0700685def get_default_user_email():
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000686 """Gets the user.email from the global .gitconfig file.
687
688 Returns:
689 User's email found in .gitconfig file, or None if none
690 """
Simon Glass7ade94e2025-03-16 08:00:18 +0000691 uemail = command.output_one_line('git', 'config', '--global', '--includes',
692 'user.email')
Vikram Narayanan12fb29a2012-05-23 09:01:06 +0000693 return uemail
694
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500695
Simon Glass761648b2022-01-29 14:14:11 -0700696def get_default_subject_prefix():
Wu, Josh9873b912015-04-15 10:25:18 +0800697 """Gets the format.subjectprefix from local .git/config file.
698
699 Returns:
700 Subject prefix found in local .git/config file, or None if none
701 """
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500702 sub_prefix = command.output_one_line(
703 'git', 'config', 'format.subjectprefix', raise_on_error=False)
Wu, Josh9873b912015-04-15 10:25:18 +0800704
705 return sub_prefix
706
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500707
Simon Glass761648b2022-01-29 14:14:11 -0700708def setup():
Simon Glass7ade94e2025-03-16 08:00:18 +0000709 """setup() - Set up git utils, by reading the alias files."""
Simon Glass26132882012-01-14 15:12:45 +0000710 # Check for a git alias file also
Simon Glass7ade94e2025-03-16 08:00:18 +0000711 global USE_NO_DECORATE
Simon Glass81bcca82014-08-28 09:43:45 -0600712
Simon Glass761648b2022-01-29 14:14:11 -0700713 cmd = log_cmd(None, count=0)
Simon Glass7ade94e2025-03-16 08:00:18 +0000714 USE_NO_DECORATE = (command.run_one(*cmd, raise_on_error=False)
Simon Glass6af913d2014-08-09 15:33:11 -0600715 .return_code == 0)
Simon Glass26132882012-01-14 15:12:45 +0000716
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500717
Simon Glass1bc26b22025-05-08 04:22:18 +0200718def get_hash(spec, git_dir=None):
Simon Glass414f1e02025-02-27 12:27:30 -0700719 """Get the hash of a commit
720
721 Args:
722 spec (str): Git commit to show, e.g. 'my-branch~12'
723
724 Returns:
725 str: Hash of commit
726 """
Simon Glass1bc26b22025-05-08 04:22:18 +0200727 cmd = ['git']
728 if git_dir:
729 cmd += ['--git-dir', git_dir]
730 cmd += ['show', '-s', '--pretty=format:%H', spec]
731 return command.output_one_line(*cmd)
Simon Glass414f1e02025-02-27 12:27:30 -0700732
733
Simon Glass761648b2022-01-29 14:14:11 -0700734def get_head():
Simon Glass11aba512012-12-15 10:42:07 +0000735 """Get the hash of the current HEAD
736
737 Returns:
738 Hash of HEAD
739 """
Simon Glass414f1e02025-02-27 12:27:30 -0700740 return get_hash('HEAD')
741
742
Simon Glass1bc26b22025-05-08 04:22:18 +0200743def get_branch(git_dir=None):
Simon Glass414f1e02025-02-27 12:27:30 -0700744 """Get the branch we are currently on
745
746 Return:
747 str: branch name, or None if none
Simon Glass1bc26b22025-05-08 04:22:18 +0200748 git_dir (str): Path to git repository (None to use default)
Simon Glass414f1e02025-02-27 12:27:30 -0700749 """
Simon Glass1bc26b22025-05-08 04:22:18 +0200750 cmd = ['git']
751 if git_dir:
752 cmd += ['--git-dir', git_dir]
753 cmd += ['rev-parse', '--abbrev-ref', 'HEAD']
754 out = command.output_one_line(*cmd, raise_on_error=False)
Simon Glass414f1e02025-02-27 12:27:30 -0700755 if out == 'HEAD':
756 return None
757 return out
Simon Glass11aba512012-12-15 10:42:07 +0000758
Maxim Cournoyer9a196fb2022-12-19 17:32:38 -0500759
Simon Glass26132882012-01-14 15:12:45 +0000760if __name__ == "__main__":
761 import doctest
762
763 doctest.testmod()