]>
Commit | Line | Data |
---|---|---|
0d24de9d SG |
1 | # Copyright (c) 2011 The Chromium OS Authors. |
2 | # | |
1a459660 | 3 | # SPDX-License-Identifier: GPL-2.0+ |
0d24de9d SG |
4 | # |
5 | ||
6 | import command | |
7 | import re | |
8 | import os | |
9 | import series | |
0d24de9d SG |
10 | import subprocess |
11 | import sys | |
12 | import terminal | |
13 | ||
757f64a8 | 14 | import checkpatch |
5f6a1c42 SG |
15 | import settings |
16 | ||
e49f14af SG |
17 | # True to use --no-decorate - we check this in Setup() |
18 | use_no_decorate = True | |
19 | ||
cda2a611 SG |
20 | def LogCmd(commit_range, git_dir=None, oneline=False, reverse=False, |
21 | count=None): | |
22 | """Create a command to perform a 'git log' | |
23 | ||
24 | Args: | |
25 | commit_range: Range expression to use for log, None for none | |
26 | git_dir: Path to git repositiory (None to use default) | |
27 | oneline: True to use --oneline, else False | |
28 | reverse: True to reverse the log (--reverse) | |
29 | count: Number of commits to list, or None for no limit | |
30 | Return: | |
31 | List containing command and arguments to run | |
32 | """ | |
33 | cmd = ['git'] | |
34 | if git_dir: | |
35 | cmd += ['--git-dir', git_dir] | |
9447a6b2 | 36 | cmd += ['--no-pager', 'log', '--no-color'] |
cda2a611 SG |
37 | if oneline: |
38 | cmd.append('--oneline') | |
e49f14af SG |
39 | if use_no_decorate: |
40 | cmd.append('--no-decorate') | |
042a732c SG |
41 | if reverse: |
42 | cmd.append('--reverse') | |
cda2a611 SG |
43 | if count is not None: |
44 | cmd.append('-n%d' % count) | |
45 | if commit_range: | |
46 | cmd.append(commit_range) | |
d4c8572b SG |
47 | |
48 | # Add this in case we have a branch with the same name as a directory. | |
49 | # This avoids messages like this, for example: | |
50 | # fatal: ambiguous argument 'test': both revision and filename | |
51 | cmd.append('--') | |
cda2a611 | 52 | return cmd |
0d24de9d SG |
53 | |
54 | def CountCommitsToBranch(): | |
55 | """Returns number of commits between HEAD and the tracking branch. | |
56 | ||
57 | This looks back to the tracking branch and works out the number of commits | |
58 | since then. | |
59 | ||
60 | Return: | |
61 | Number of patches that exist on top of the branch | |
62 | """ | |
cda2a611 | 63 | pipe = [LogCmd('@{upstream}..', oneline=True), |
0d24de9d | 64 | ['wc', '-l']] |
a10fd93c | 65 | stdout = command.RunPipe(pipe, capture=True, oneline=True).stdout |
0d24de9d SG |
66 | patch_count = int(stdout) |
67 | return patch_count | |
68 | ||
2a9e2c6a SG |
69 | def NameRevision(commit_hash): |
70 | """Gets the revision name for a commit | |
71 | ||
72 | Args: | |
73 | commit_hash: Commit hash to look up | |
74 | ||
75 | Return: | |
76 | Name of revision, if any, else None | |
77 | """ | |
78 | pipe = ['git', 'name-rev', commit_hash] | |
79 | stdout = command.RunPipe([pipe], capture=True, oneline=True).stdout | |
80 | ||
81 | # We expect a commit, a space, then a revision name | |
82 | name = stdout.split(' ')[1].strip() | |
83 | return name | |
84 | ||
85 | def GuessUpstream(git_dir, branch): | |
86 | """Tries to guess the upstream for a branch | |
87 | ||
88 | This lists out top commits on a branch and tries to find a suitable | |
89 | upstream. It does this by looking for the first commit where | |
90 | 'git name-rev' returns a plain branch name, with no ! or ^ modifiers. | |
91 | ||
92 | Args: | |
93 | git_dir: Git directory containing repo | |
94 | branch: Name of branch | |
95 | ||
96 | Returns: | |
97 | Tuple: | |
98 | Name of upstream branch (e.g. 'upstream/master') or None if none | |
99 | Warning/error message, or None if none | |
100 | """ | |
101 | pipe = [LogCmd(branch, git_dir=git_dir, oneline=True, count=100)] | |
102 | result = command.RunPipe(pipe, capture=True, capture_stderr=True, | |
103 | raise_on_error=False) | |
104 | if result.return_code: | |
105 | return None, "Branch '%s' not found" % branch | |
106 | for line in result.stdout.splitlines()[1:]: | |
107 | commit_hash = line.split(' ')[0] | |
108 | name = NameRevision(commit_hash) | |
109 | if '~' not in name and '^' not in name: | |
110 | if name.startswith('remotes/'): | |
111 | name = name[8:] | |
112 | return name, "Guessing upstream as '%s'" % name | |
113 | return None, "Cannot find a suitable upstream for branch '%s'" % branch | |
114 | ||
5f6a1c42 SG |
115 | def GetUpstream(git_dir, branch): |
116 | """Returns the name of the upstream for a branch | |
117 | ||
118 | Args: | |
119 | git_dir: Git directory containing repo | |
120 | branch: Name of branch | |
121 | ||
122 | Returns: | |
2a9e2c6a SG |
123 | Tuple: |
124 | Name of upstream branch (e.g. 'upstream/master') or None if none | |
125 | Warning/error message, or None if none | |
5f6a1c42 | 126 | """ |
cce717a9 SG |
127 | try: |
128 | remote = command.OutputOneLine('git', '--git-dir', git_dir, 'config', | |
129 | 'branch.%s.remote' % branch) | |
130 | merge = command.OutputOneLine('git', '--git-dir', git_dir, 'config', | |
131 | 'branch.%s.merge' % branch) | |
132 | except: | |
2a9e2c6a SG |
133 | upstream, msg = GuessUpstream(git_dir, branch) |
134 | return upstream, msg | |
cce717a9 | 135 | |
5f6a1c42 | 136 | if remote == '.': |
71edbe5c | 137 | return merge, None |
5f6a1c42 SG |
138 | elif remote and merge: |
139 | leaf = merge.split('/')[-1] | |
2a9e2c6a | 140 | return '%s/%s' % (remote, leaf), None |
5f6a1c42 | 141 | else: |
ac3fde93 | 142 | raise ValueError("Cannot determine upstream branch for branch " |
5f6a1c42 SG |
143 | "'%s' remote='%s', merge='%s'" % (branch, remote, merge)) |
144 | ||
145 | ||
146 | def GetRangeInBranch(git_dir, branch, include_upstream=False): | |
147 | """Returns an expression for the commits in the given branch. | |
148 | ||
149 | Args: | |
150 | git_dir: Directory containing git repo | |
151 | branch: Name of branch | |
152 | Return: | |
153 | Expression in the form 'upstream..branch' which can be used to | |
cce717a9 | 154 | access the commits. If the branch does not exist, returns None. |
5f6a1c42 | 155 | """ |
2a9e2c6a | 156 | upstream, msg = GetUpstream(git_dir, branch) |
cce717a9 | 157 | if not upstream: |
2a9e2c6a SG |
158 | return None, msg |
159 | rstr = '%s%s..%s' % (upstream, '~' if include_upstream else '', branch) | |
160 | return rstr, msg | |
5f6a1c42 | 161 | |
5abab20d SG |
162 | def CountCommitsInRange(git_dir, range_expr): |
163 | """Returns the number of commits in the given range. | |
164 | ||
165 | Args: | |
166 | git_dir: Directory containing git repo | |
167 | range_expr: Range to check | |
168 | Return: | |
169 | Number of patches that exist in the supplied rangem or None if none | |
170 | were found | |
171 | """ | |
172 | pipe = [LogCmd(range_expr, git_dir=git_dir, oneline=True)] | |
173 | result = command.RunPipe(pipe, capture=True, capture_stderr=True, | |
174 | raise_on_error=False) | |
175 | if result.return_code: | |
176 | return None, "Range '%s' not found or is invalid" % range_expr | |
177 | patch_count = len(result.stdout.splitlines()) | |
178 | return patch_count, None | |
179 | ||
5f6a1c42 SG |
180 | def CountCommitsInBranch(git_dir, branch, include_upstream=False): |
181 | """Returns the number of commits in the given branch. | |
182 | ||
183 | Args: | |
184 | git_dir: Directory containing git repo | |
185 | branch: Name of branch | |
186 | Return: | |
cce717a9 SG |
187 | Number of patches that exist on top of the branch, or None if the |
188 | branch does not exist. | |
5f6a1c42 | 189 | """ |
2a9e2c6a | 190 | range_expr, msg = GetRangeInBranch(git_dir, branch, include_upstream) |
cce717a9 | 191 | if not range_expr: |
2a9e2c6a | 192 | return None, msg |
5abab20d | 193 | return CountCommitsInRange(git_dir, range_expr) |
5f6a1c42 SG |
194 | |
195 | def CountCommits(commit_range): | |
196 | """Returns the number of commits in the given range. | |
197 | ||
198 | Args: | |
199 | commit_range: Range of commits to count (e.g. 'HEAD..base') | |
200 | Return: | |
201 | Number of patches that exist on top of the branch | |
202 | """ | |
cda2a611 | 203 | pipe = [LogCmd(commit_range, oneline=True), |
5f6a1c42 SG |
204 | ['wc', '-l']] |
205 | stdout = command.RunPipe(pipe, capture=True, oneline=True).stdout | |
206 | patch_count = int(stdout) | |
207 | return patch_count | |
208 | ||
209 | def Checkout(commit_hash, git_dir=None, work_tree=None, force=False): | |
210 | """Checkout the selected commit for this build | |
211 | ||
212 | Args: | |
213 | commit_hash: Commit hash to check out | |
214 | """ | |
215 | pipe = ['git'] | |
216 | if git_dir: | |
217 | pipe.extend(['--git-dir', git_dir]) | |
218 | if work_tree: | |
219 | pipe.extend(['--work-tree', work_tree]) | |
220 | pipe.append('checkout') | |
221 | if force: | |
222 | pipe.append('-f') | |
223 | pipe.append(commit_hash) | |
ddaf5c8f SG |
224 | result = command.RunPipe([pipe], capture=True, raise_on_error=False, |
225 | capture_stderr=True) | |
5f6a1c42 | 226 | if result.return_code != 0: |
ac3fde93 | 227 | raise OSError('git checkout (%s): %s' % (pipe, result.stderr)) |
5f6a1c42 SG |
228 | |
229 | def Clone(git_dir, output_dir): | |
230 | """Checkout the selected commit for this build | |
231 | ||
232 | Args: | |
233 | commit_hash: Commit hash to check out | |
234 | """ | |
235 | pipe = ['git', 'clone', git_dir, '.'] | |
ddaf5c8f SG |
236 | result = command.RunPipe([pipe], capture=True, cwd=output_dir, |
237 | capture_stderr=True) | |
5f6a1c42 | 238 | if result.return_code != 0: |
ac3fde93 | 239 | raise OSError('git clone: %s' % result.stderr) |
5f6a1c42 SG |
240 | |
241 | def Fetch(git_dir=None, work_tree=None): | |
242 | """Fetch from the origin repo | |
243 | ||
244 | Args: | |
245 | commit_hash: Commit hash to check out | |
246 | """ | |
247 | pipe = ['git'] | |
248 | if git_dir: | |
249 | pipe.extend(['--git-dir', git_dir]) | |
250 | if work_tree: | |
251 | pipe.extend(['--work-tree', work_tree]) | |
252 | pipe.append('fetch') | |
ddaf5c8f | 253 | result = command.RunPipe([pipe], capture=True, capture_stderr=True) |
5f6a1c42 | 254 | if result.return_code != 0: |
ac3fde93 | 255 | raise OSError('git fetch: %s' % result.stderr) |
5f6a1c42 | 256 | |
0d24de9d SG |
257 | def CreatePatches(start, count, series): |
258 | """Create a series of patches from the top of the current branch. | |
259 | ||
260 | The patch files are written to the current directory using | |
261 | git format-patch. | |
262 | ||
263 | Args: | |
264 | start: Commit to start from: 0=HEAD, 1=next one, etc. | |
265 | count: number of commits to include | |
266 | Return: | |
267 | Filename of cover letter | |
268 | List of filenames of patch files | |
269 | """ | |
270 | if series.get('version'): | |
271 | version = '%s ' % series['version'] | |
8d3595a4 | 272 | cmd = ['git', 'format-patch', '-M', '--signoff'] |
0d24de9d SG |
273 | if series.get('cover'): |
274 | cmd.append('--cover-letter') | |
275 | prefix = series.GetPatchPrefix() | |
276 | if prefix: | |
277 | cmd += ['--subject-prefix=%s' % prefix] | |
278 | cmd += ['HEAD~%d..HEAD~%d' % (start + count, start)] | |
279 | ||
280 | stdout = command.RunList(cmd) | |
281 | files = stdout.splitlines() | |
282 | ||
283 | # We have an extra file if there is a cover letter | |
284 | if series.get('cover'): | |
285 | return files[0], files[1:] | |
286 | else: | |
287 | return None, files | |
288 | ||
a1318f7c | 289 | def BuildEmailList(in_list, tag=None, alias=None, raise_on_error=True): |
0d24de9d SG |
290 | """Build a list of email addresses based on an input list. |
291 | ||
292 | Takes a list of email addresses and aliases, and turns this into a list | |
293 | of only email address, by resolving any aliases that are present. | |
294 | ||
295 | If the tag is given, then each email address is prepended with this | |
296 | tag and a space. If the tag starts with a minus sign (indicating a | |
297 | command line parameter) then the email address is quoted. | |
298 | ||
299 | Args: | |
300 | in_list: List of aliases/email addresses | |
301 | tag: Text to put before each address | |
a1318f7c SG |
302 | alias: Alias dictionary |
303 | raise_on_error: True to raise an error when an alias fails to match, | |
304 | False to just print a message. | |
0d24de9d SG |
305 | |
306 | Returns: | |
307 | List of email addresses | |
308 | ||
309 | >>> alias = {} | |
310 | >>> alias['fred'] = ['f.bloggs@napier.co.nz'] | |
311 | >>> alias['john'] = ['j.bloggs@napier.co.nz'] | |
312 | >>> alias['mary'] = ['Mary Poppins <m.poppins@cloud.net>'] | |
313 | >>> alias['boys'] = ['fred', ' john'] | |
314 | >>> alias['all'] = ['fred ', 'john', ' mary '] | |
315 | >>> BuildEmailList(['john', 'mary'], None, alias) | |
316 | ['j.bloggs@napier.co.nz', 'Mary Poppins <m.poppins@cloud.net>'] | |
317 | >>> BuildEmailList(['john', 'mary'], '--to', alias) | |
318 | ['--to "j.bloggs@napier.co.nz"', \ | |
319 | '--to "Mary Poppins <m.poppins@cloud.net>"'] | |
320 | >>> BuildEmailList(['john', 'mary'], 'Cc', alias) | |
321 | ['Cc j.bloggs@napier.co.nz', 'Cc Mary Poppins <m.poppins@cloud.net>'] | |
322 | """ | |
323 | quote = '"' if tag and tag[0] == '-' else '' | |
324 | raw = [] | |
325 | for item in in_list: | |
a1318f7c | 326 | raw += LookupEmail(item, alias, raise_on_error=raise_on_error) |
0d24de9d SG |
327 | result = [] |
328 | for item in raw: | |
329 | if not item in result: | |
330 | result.append(item) | |
331 | if tag: | |
332 | return ['%s %s%s%s' % (tag, quote, email, quote) for email in result] | |
333 | return result | |
334 | ||
a1318f7c | 335 | def EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname, |
27067a46 | 336 | self_only=False, alias=None, in_reply_to=None, thread=False): |
0d24de9d SG |
337 | """Email a patch series. |
338 | ||
339 | Args: | |
340 | series: Series object containing destination info | |
341 | cover_fname: filename of cover letter | |
342 | args: list of filenames of patch files | |
343 | dry_run: Just return the command that would be run | |
a1318f7c SG |
344 | raise_on_error: True to raise an error when an alias fails to match, |
345 | False to just print a message. | |
0d24de9d SG |
346 | cc_fname: Filename of Cc file for per-commit Cc |
347 | self_only: True to just email to yourself as a test | |
6d819925 DA |
348 | in_reply_to: If set we'll pass this to git as --in-reply-to. |
349 | Should be a message ID that this is in reply to. | |
27067a46 MK |
350 | thread: True to add --thread to git send-email (make |
351 | all patches reply to cover-letter or first patch in series) | |
0d24de9d SG |
352 | |
353 | Returns: | |
354 | Git command that was/would be run | |
355 | ||
a970048e DA |
356 | # For the duration of this doctest pretend that we ran patman with ./patman |
357 | >>> _old_argv0 = sys.argv[0] | |
358 | >>> sys.argv[0] = './patman' | |
359 | ||
0d24de9d SG |
360 | >>> alias = {} |
361 | >>> alias['fred'] = ['f.bloggs@napier.co.nz'] | |
362 | >>> alias['john'] = ['j.bloggs@napier.co.nz'] | |
363 | >>> alias['mary'] = ['m.poppins@cloud.net'] | |
364 | >>> alias['boys'] = ['fred', ' john'] | |
365 | >>> alias['all'] = ['fred ', 'john', ' mary '] | |
366 | >>> alias[os.getenv('USER')] = ['this-is-me@me.com'] | |
367 | >>> series = series.Series() | |
368 | >>> series.to = ['fred'] | |
369 | >>> series.cc = ['mary'] | |
a1318f7c SG |
370 | >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ |
371 | False, alias) | |
0d24de9d SG |
372 | 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ |
373 | "m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2' | |
a1318f7c SG |
374 | >>> EmailPatches(series, None, ['p1'], True, True, 'cc-fname', False, \ |
375 | alias) | |
0d24de9d SG |
376 | 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ |
377 | "m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" p1' | |
378 | >>> series.cc = ['all'] | |
a1318f7c SG |
379 | >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ |
380 | True, alias) | |
0d24de9d SG |
381 | 'git send-email --annotate --to "this-is-me@me.com" --cc-cmd "./patman \ |
382 | --cc-cmd cc-fname" cover p1 p2' | |
a1318f7c SG |
383 | >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ |
384 | False, alias) | |
0d24de9d SG |
385 | 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \ |
386 | "f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \ | |
387 | "m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2' | |
a970048e DA |
388 | |
389 | # Restore argv[0] since we clobbered it. | |
390 | >>> sys.argv[0] = _old_argv0 | |
0d24de9d | 391 | """ |
a1318f7c | 392 | to = BuildEmailList(series.get('to'), '--to', alias, raise_on_error) |
0d24de9d | 393 | if not to: |
785f1548 SG |
394 | git_config_to = command.Output('git', 'config', 'sendemail.to', |
395 | raise_on_error=False) | |
ee860c60 MY |
396 | if not git_config_to: |
397 | print ("No recipient.\n" | |
398 | "Please add something like this to a commit\n" | |
399 | "Series-to: Fred Bloggs <f.blogs@napier.co.nz>\n" | |
400 | "Or do something like this\n" | |
401 | "git config sendemail.to u-boot@lists.denx.de") | |
402 | return | |
2181830f PT |
403 | cc = BuildEmailList(list(set(series.get('cc')) - set(series.get('to'))), |
404 | '--cc', alias, raise_on_error) | |
0d24de9d | 405 | if self_only: |
a1318f7c | 406 | to = BuildEmailList([os.getenv('USER')], '--to', alias, raise_on_error) |
0d24de9d SG |
407 | cc = [] |
408 | cmd = ['git', 'send-email', '--annotate'] | |
6d819925 | 409 | if in_reply_to: |
6f8abf76 SG |
410 | if type(in_reply_to) != str: |
411 | in_reply_to = in_reply_to.encode('utf-8') | |
6d819925 | 412 | cmd.append('--in-reply-to="%s"' % in_reply_to) |
27067a46 MK |
413 | if thread: |
414 | cmd.append('--thread') | |
6d819925 | 415 | |
0d24de9d SG |
416 | cmd += to |
417 | cmd += cc | |
418 | cmd += ['--cc-cmd', '"%s --cc-cmd %s"' % (sys.argv[0], cc_fname)] | |
419 | if cover_fname: | |
420 | cmd.append(cover_fname) | |
421 | cmd += args | |
422 | str = ' '.join(cmd) | |
423 | if not dry_run: | |
424 | os.system(str) | |
425 | return str | |
426 | ||
427 | ||
a1318f7c | 428 | def LookupEmail(lookup_name, alias=None, raise_on_error=True, level=0): |
0d24de9d SG |
429 | """If an email address is an alias, look it up and return the full name |
430 | ||
431 | TODO: Why not just use git's own alias feature? | |
432 | ||
433 | Args: | |
434 | lookup_name: Alias or email address to look up | |
a1318f7c SG |
435 | alias: Dictionary containing aliases (None to use settings default) |
436 | raise_on_error: True to raise an error when an alias fails to match, | |
437 | False to just print a message. | |
0d24de9d SG |
438 | |
439 | Returns: | |
440 | tuple: | |
441 | list containing a list of email addresses | |
442 | ||
443 | Raises: | |
444 | OSError if a recursive alias reference was found | |
445 | ValueError if an alias was not found | |
446 | ||
447 | >>> alias = {} | |
448 | >>> alias['fred'] = ['f.bloggs@napier.co.nz'] | |
449 | >>> alias['john'] = ['j.bloggs@napier.co.nz'] | |
450 | >>> alias['mary'] = ['m.poppins@cloud.net'] | |
451 | >>> alias['boys'] = ['fred', ' john', 'f.bloggs@napier.co.nz'] | |
452 | >>> alias['all'] = ['fred ', 'john', ' mary '] | |
453 | >>> alias['loop'] = ['other', 'john', ' mary '] | |
454 | >>> alias['other'] = ['loop', 'john', ' mary '] | |
455 | >>> LookupEmail('mary', alias) | |
456 | ['m.poppins@cloud.net'] | |
457 | >>> LookupEmail('arthur.wellesley@howe.ro.uk', alias) | |
458 | ['arthur.wellesley@howe.ro.uk'] | |
459 | >>> LookupEmail('boys', alias) | |
460 | ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz'] | |
461 | >>> LookupEmail('all', alias) | |
462 | ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz', 'm.poppins@cloud.net'] | |
463 | >>> LookupEmail('odd', alias) | |
464 | Traceback (most recent call last): | |
465 | ... | |
466 | ValueError: Alias 'odd' not found | |
467 | >>> LookupEmail('loop', alias) | |
468 | Traceback (most recent call last): | |
469 | ... | |
470 | OSError: Recursive email alias at 'other' | |
a1318f7c | 471 | >>> LookupEmail('odd', alias, raise_on_error=False) |
e752edcb | 472 | Alias 'odd' not found |
a1318f7c SG |
473 | [] |
474 | >>> # In this case the loop part will effectively be ignored. | |
475 | >>> LookupEmail('loop', alias, raise_on_error=False) | |
e752edcb SG |
476 | Recursive email alias at 'other' |
477 | Recursive email alias at 'john' | |
478 | Recursive email alias at 'mary' | |
a1318f7c | 479 | ['j.bloggs@napier.co.nz', 'm.poppins@cloud.net'] |
0d24de9d SG |
480 | """ |
481 | if not alias: | |
482 | alias = settings.alias | |
483 | lookup_name = lookup_name.strip() | |
484 | if '@' in lookup_name: # Perhaps a real email address | |
485 | return [lookup_name] | |
486 | ||
487 | lookup_name = lookup_name.lower() | |
a1318f7c | 488 | col = terminal.Color() |
0d24de9d | 489 | |
a1318f7c | 490 | out_list = [] |
0d24de9d | 491 | if level > 10: |
a1318f7c SG |
492 | msg = "Recursive email alias at '%s'" % lookup_name |
493 | if raise_on_error: | |
ac3fde93 | 494 | raise OSError(msg) |
a1318f7c | 495 | else: |
a920a17b | 496 | print(col.Color(col.RED, msg)) |
a1318f7c | 497 | return out_list |
0d24de9d | 498 | |
0d24de9d SG |
499 | if lookup_name: |
500 | if not lookup_name in alias: | |
a1318f7c SG |
501 | msg = "Alias '%s' not found" % lookup_name |
502 | if raise_on_error: | |
ac3fde93 | 503 | raise ValueError(msg) |
a1318f7c | 504 | else: |
a920a17b | 505 | print(col.Color(col.RED, msg)) |
a1318f7c | 506 | return out_list |
0d24de9d | 507 | for item in alias[lookup_name]: |
a1318f7c | 508 | todo = LookupEmail(item, alias, raise_on_error, level + 1) |
0d24de9d SG |
509 | for new_item in todo: |
510 | if not new_item in out_list: | |
511 | out_list.append(new_item) | |
512 | ||
a920a17b | 513 | #print("No match for alias '%s'" % lookup_name) |
0d24de9d SG |
514 | return out_list |
515 | ||
516 | def GetTopLevel(): | |
517 | """Return name of top-level directory for this git repo. | |
518 | ||
519 | Returns: | |
520 | Full path to git top-level directory | |
521 | ||
522 | This test makes sure that we are running tests in the right subdir | |
523 | ||
a970048e DA |
524 | >>> os.path.realpath(os.path.dirname(__file__)) == \ |
525 | os.path.join(GetTopLevel(), 'tools', 'patman') | |
0d24de9d SG |
526 | True |
527 | """ | |
528 | return command.OutputOneLine('git', 'rev-parse', '--show-toplevel') | |
529 | ||
530 | def GetAliasFile(): | |
531 | """Gets the name of the git alias file. | |
532 | ||
533 | Returns: | |
534 | Filename of git alias file, or None if none | |
535 | """ | |
dc191505 SG |
536 | fname = command.OutputOneLine('git', 'config', 'sendemail.aliasesfile', |
537 | raise_on_error=False) | |
0d24de9d SG |
538 | if fname: |
539 | fname = os.path.join(GetTopLevel(), fname.strip()) | |
540 | return fname | |
541 | ||
87d65558 VN |
542 | def GetDefaultUserName(): |
543 | """Gets the user.name from .gitconfig file. | |
544 | ||
545 | Returns: | |
546 | User name found in .gitconfig file, or None if none | |
547 | """ | |
548 | uname = command.OutputOneLine('git', 'config', '--global', 'user.name') | |
549 | return uname | |
550 | ||
551 | def GetDefaultUserEmail(): | |
552 | """Gets the user.email from the global .gitconfig file. | |
553 | ||
554 | Returns: | |
555 | User's email found in .gitconfig file, or None if none | |
556 | """ | |
557 | uemail = command.OutputOneLine('git', 'config', '--global', 'user.email') | |
558 | return uemail | |
559 | ||
3871cd85 WJ |
560 | def GetDefaultSubjectPrefix(): |
561 | """Gets the format.subjectprefix from local .git/config file. | |
562 | ||
563 | Returns: | |
564 | Subject prefix found in local .git/config file, or None if none | |
565 | """ | |
566 | sub_prefix = command.OutputOneLine('git', 'config', 'format.subjectprefix', | |
567 | raise_on_error=False) | |
568 | ||
569 | return sub_prefix | |
570 | ||
0d24de9d SG |
571 | def Setup(): |
572 | """Set up git utils, by reading the alias files.""" | |
0d24de9d | 573 | # Check for a git alias file also |
0b703dbc SG |
574 | global use_no_decorate |
575 | ||
0d24de9d SG |
576 | alias_fname = GetAliasFile() |
577 | if alias_fname: | |
578 | settings.ReadGitAliases(alias_fname) | |
e49f14af SG |
579 | cmd = LogCmd(None, count=0) |
580 | use_no_decorate = (command.RunPipe([cmd], raise_on_error=False) | |
581 | .return_code == 0) | |
0d24de9d | 582 | |
5f6a1c42 SG |
583 | def GetHead(): |
584 | """Get the hash of the current HEAD | |
585 | ||
586 | Returns: | |
587 | Hash of HEAD | |
588 | """ | |
589 | return command.OutputOneLine('git', 'show', '-s', '--pretty=format:%H') | |
590 | ||
0d24de9d SG |
591 | if __name__ == "__main__": |
592 | import doctest | |
593 | ||
594 | doctest.testmod() |