blob: b7f673955218ef3a49aee8ad5427353b8e6c2a27 [file] [log] [blame]
Simon Glass0d24de92012-01-14 15:12:45 +00001# Copyright (c) 2011 The Chromium OS Authors.
2#
3# See file CREDITS for list of people who contributed to this
4# project.
5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License as
8# published by the Free Software Foundation; either version 2 of
9# the License, or (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston,
19# MA 02111-1307 USA
20#
21
22import command
23import re
24import os
25import series
Simon Glass0d24de92012-01-14 15:12:45 +000026import subprocess
27import sys
28import terminal
29
Simon Glass5f6a1c42012-12-15 10:42:07 +000030import settings
31
Simon Glass0d24de92012-01-14 15:12:45 +000032
33def CountCommitsToBranch():
34 """Returns number of commits between HEAD and the tracking branch.
35
36 This looks back to the tracking branch and works out the number of commits
37 since then.
38
39 Return:
40 Number of patches that exist on top of the branch
41 """
Andreas Bießmann23860602013-04-15 23:52:18 +000042 pipe = [['git', 'log', '--no-color', '--oneline', '--no-decorate',
43 '@{upstream}..'],
Simon Glass0d24de92012-01-14 15:12:45 +000044 ['wc', '-l']]
Simon Glassa10fd932012-12-15 10:42:04 +000045 stdout = command.RunPipe(pipe, capture=True, oneline=True).stdout
Simon Glass0d24de92012-01-14 15:12:45 +000046 patch_count = int(stdout)
47 return patch_count
48
Simon Glass5f6a1c42012-12-15 10:42:07 +000049def GetUpstream(git_dir, branch):
50 """Returns the name of the upstream for a branch
51
52 Args:
53 git_dir: Git directory containing repo
54 branch: Name of branch
55
56 Returns:
57 Name of upstream branch (e.g. 'upstream/master') or None if none
58 """
Simon Glasscce717a2013-05-08 08:06:08 +000059 try:
60 remote = command.OutputOneLine('git', '--git-dir', git_dir, 'config',
61 'branch.%s.remote' % branch)
62 merge = command.OutputOneLine('git', '--git-dir', git_dir, 'config',
63 'branch.%s.merge' % branch)
64 except:
65 return None
66
Simon Glass5f6a1c42012-12-15 10:42:07 +000067 if remote == '.':
68 return merge
69 elif remote and merge:
70 leaf = merge.split('/')[-1]
71 return '%s/%s' % (remote, leaf)
72 else:
73 raise ValueError, ("Cannot determine upstream branch for branch "
74 "'%s' remote='%s', merge='%s'" % (branch, remote, merge))
75
76
77def GetRangeInBranch(git_dir, branch, include_upstream=False):
78 """Returns an expression for the commits in the given branch.
79
80 Args:
81 git_dir: Directory containing git repo
82 branch: Name of branch
83 Return:
84 Expression in the form 'upstream..branch' which can be used to
Simon Glasscce717a2013-05-08 08:06:08 +000085 access the commits. If the branch does not exist, returns None.
Simon Glass5f6a1c42012-12-15 10:42:07 +000086 """
87 upstream = GetUpstream(git_dir, branch)
Simon Glasscce717a2013-05-08 08:06:08 +000088 if not upstream:
89 return None
Simon Glass5f6a1c42012-12-15 10:42:07 +000090 return '%s%s..%s' % (upstream, '~' if include_upstream else '', branch)
91
92def CountCommitsInBranch(git_dir, branch, include_upstream=False):
93 """Returns the number of commits in the given branch.
94
95 Args:
96 git_dir: Directory containing git repo
97 branch: Name of branch
98 Return:
Simon Glasscce717a2013-05-08 08:06:08 +000099 Number of patches that exist on top of the branch, or None if the
100 branch does not exist.
Simon Glass5f6a1c42012-12-15 10:42:07 +0000101 """
102 range_expr = GetRangeInBranch(git_dir, branch, include_upstream)
Simon Glasscce717a2013-05-08 08:06:08 +0000103 if not range_expr:
104 return None
Andreas Bießmann23860602013-04-15 23:52:18 +0000105 pipe = [['git', '--git-dir', git_dir, 'log', '--oneline', '--no-decorate',
106 range_expr],
Simon Glass5f6a1c42012-12-15 10:42:07 +0000107 ['wc', '-l']]
108 result = command.RunPipe(pipe, capture=True, oneline=True)
109 patch_count = int(result.stdout)
110 return patch_count
111
112def CountCommits(commit_range):
113 """Returns the number of commits in the given range.
114
115 Args:
116 commit_range: Range of commits to count (e.g. 'HEAD..base')
117 Return:
118 Number of patches that exist on top of the branch
119 """
Andreas Bießmann23860602013-04-15 23:52:18 +0000120 pipe = [['git', 'log', '--oneline', '--no-decorate', commit_range],
Simon Glass5f6a1c42012-12-15 10:42:07 +0000121 ['wc', '-l']]
122 stdout = command.RunPipe(pipe, capture=True, oneline=True).stdout
123 patch_count = int(stdout)
124 return patch_count
125
126def Checkout(commit_hash, git_dir=None, work_tree=None, force=False):
127 """Checkout the selected commit for this build
128
129 Args:
130 commit_hash: Commit hash to check out
131 """
132 pipe = ['git']
133 if git_dir:
134 pipe.extend(['--git-dir', git_dir])
135 if work_tree:
136 pipe.extend(['--work-tree', work_tree])
137 pipe.append('checkout')
138 if force:
139 pipe.append('-f')
140 pipe.append(commit_hash)
141 result = command.RunPipe([pipe], capture=True, raise_on_error=False)
142 if result.return_code != 0:
143 raise OSError, 'git checkout (%s): %s' % (pipe, result.stderr)
144
145def Clone(git_dir, output_dir):
146 """Checkout the selected commit for this build
147
148 Args:
149 commit_hash: Commit hash to check out
150 """
151 pipe = ['git', 'clone', git_dir, '.']
152 result = command.RunPipe([pipe], capture=True, cwd=output_dir)
153 if result.return_code != 0:
154 raise OSError, 'git clone: %s' % result.stderr
155
156def Fetch(git_dir=None, work_tree=None):
157 """Fetch from the origin repo
158
159 Args:
160 commit_hash: Commit hash to check out
161 """
162 pipe = ['git']
163 if git_dir:
164 pipe.extend(['--git-dir', git_dir])
165 if work_tree:
166 pipe.extend(['--work-tree', work_tree])
167 pipe.append('fetch')
168 result = command.RunPipe([pipe], capture=True)
169 if result.return_code != 0:
170 raise OSError, 'git fetch: %s' % result.stderr
171
Simon Glass0d24de92012-01-14 15:12:45 +0000172def CreatePatches(start, count, series):
173 """Create a series of patches from the top of the current branch.
174
175 The patch files are written to the current directory using
176 git format-patch.
177
178 Args:
179 start: Commit to start from: 0=HEAD, 1=next one, etc.
180 count: number of commits to include
181 Return:
182 Filename of cover letter
183 List of filenames of patch files
184 """
185 if series.get('version'):
186 version = '%s ' % series['version']
187 cmd = ['git', 'format-patch', '-M', '--signoff']
188 if series.get('cover'):
189 cmd.append('--cover-letter')
190 prefix = series.GetPatchPrefix()
191 if prefix:
192 cmd += ['--subject-prefix=%s' % prefix]
193 cmd += ['HEAD~%d..HEAD~%d' % (start + count, start)]
194
195 stdout = command.RunList(cmd)
196 files = stdout.splitlines()
197
198 # We have an extra file if there is a cover letter
199 if series.get('cover'):
200 return files[0], files[1:]
201 else:
202 return None, files
203
204def ApplyPatch(verbose, fname):
205 """Apply a patch with git am to test it
206
207 TODO: Convert these to use command, with stderr option
208
209 Args:
210 fname: filename of patch file to apply
211 """
212 cmd = ['git', 'am', fname]
213 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
214 stderr=subprocess.PIPE)
215 stdout, stderr = pipe.communicate()
216 re_error = re.compile('^error: patch failed: (.+):(\d+)')
217 for line in stderr.splitlines():
218 if verbose:
219 print line
220 match = re_error.match(line)
221 if match:
222 print GetWarningMsg('warning', match.group(1), int(match.group(2)),
223 'Patch failed')
224 return pipe.returncode == 0, stdout
225
226def ApplyPatches(verbose, args, start_point):
227 """Apply the patches with git am to make sure all is well
228
229 Args:
230 verbose: Print out 'git am' output verbatim
231 args: List of patch files to apply
232 start_point: Number of commits back from HEAD to start applying.
233 Normally this is len(args), but it can be larger if a start
234 offset was given.
235 """
236 error_count = 0
237 col = terminal.Color()
238
239 # Figure out our current position
240 cmd = ['git', 'name-rev', 'HEAD', '--name-only']
241 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE)
242 stdout, stderr = pipe.communicate()
243 if pipe.returncode:
244 str = 'Could not find current commit name'
245 print col.Color(col.RED, str)
246 print stdout
247 return False
248 old_head = stdout.splitlines()[0]
249
250 # Checkout the required start point
251 cmd = ['git', 'checkout', 'HEAD~%d' % start_point]
252 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
253 stderr=subprocess.PIPE)
254 stdout, stderr = pipe.communicate()
255 if pipe.returncode:
256 str = 'Could not move to commit before patch series'
257 print col.Color(col.RED, str)
258 print stdout, stderr
259 return False
260
261 # Apply all the patches
262 for fname in args:
263 ok, stdout = ApplyPatch(verbose, fname)
264 if not ok:
265 print col.Color(col.RED, 'git am returned errors for %s: will '
266 'skip this patch' % fname)
267 if verbose:
268 print stdout
269 error_count += 1
270 cmd = ['git', 'am', '--skip']
271 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE)
272 stdout, stderr = pipe.communicate()
273 if pipe.returncode != 0:
274 print col.Color(col.RED, 'Unable to skip patch! Aborting...')
275 print stdout
276 break
277
278 # Return to our previous position
279 cmd = ['git', 'checkout', old_head]
280 pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
281 stdout, stderr = pipe.communicate()
282 if pipe.returncode:
283 print col.Color(col.RED, 'Could not move back to head commit')
284 print stdout, stderr
285 return error_count == 0
286
Simon Glassa1318f72013-03-26 13:09:42 +0000287def BuildEmailList(in_list, tag=None, alias=None, raise_on_error=True):
Simon Glass0d24de92012-01-14 15:12:45 +0000288 """Build a list of email addresses based on an input list.
289
290 Takes a list of email addresses and aliases, and turns this into a list
291 of only email address, by resolving any aliases that are present.
292
293 If the tag is given, then each email address is prepended with this
294 tag and a space. If the tag starts with a minus sign (indicating a
295 command line parameter) then the email address is quoted.
296
297 Args:
298 in_list: List of aliases/email addresses
299 tag: Text to put before each address
Simon Glassa1318f72013-03-26 13:09:42 +0000300 alias: Alias dictionary
301 raise_on_error: True to raise an error when an alias fails to match,
302 False to just print a message.
Simon Glass0d24de92012-01-14 15:12:45 +0000303
304 Returns:
305 List of email addresses
306
307 >>> alias = {}
308 >>> alias['fred'] = ['f.bloggs@napier.co.nz']
309 >>> alias['john'] = ['j.bloggs@napier.co.nz']
310 >>> alias['mary'] = ['Mary Poppins <m.poppins@cloud.net>']
311 >>> alias['boys'] = ['fred', ' john']
312 >>> alias['all'] = ['fred ', 'john', ' mary ']
313 >>> BuildEmailList(['john', 'mary'], None, alias)
314 ['j.bloggs@napier.co.nz', 'Mary Poppins <m.poppins@cloud.net>']
315 >>> BuildEmailList(['john', 'mary'], '--to', alias)
316 ['--to "j.bloggs@napier.co.nz"', \
317'--to "Mary Poppins <m.poppins@cloud.net>"']
318 >>> BuildEmailList(['john', 'mary'], 'Cc', alias)
319 ['Cc j.bloggs@napier.co.nz', 'Cc Mary Poppins <m.poppins@cloud.net>']
320 """
321 quote = '"' if tag and tag[0] == '-' else ''
322 raw = []
323 for item in in_list:
Simon Glassa1318f72013-03-26 13:09:42 +0000324 raw += LookupEmail(item, alias, raise_on_error=raise_on_error)
Simon Glass0d24de92012-01-14 15:12:45 +0000325 result = []
326 for item in raw:
327 if not item in result:
328 result.append(item)
329 if tag:
330 return ['%s %s%s%s' % (tag, quote, email, quote) for email in result]
331 return result
332
Simon Glassa1318f72013-03-26 13:09:42 +0000333def EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname,
Doug Anderson6d819922013-03-17 10:31:04 +0000334 self_only=False, alias=None, in_reply_to=None):
Simon Glass0d24de92012-01-14 15:12:45 +0000335 """Email a patch series.
336
337 Args:
338 series: Series object containing destination info
339 cover_fname: filename of cover letter
340 args: list of filenames of patch files
341 dry_run: Just return the command that would be run
Simon Glassa1318f72013-03-26 13:09:42 +0000342 raise_on_error: True to raise an error when an alias fails to match,
343 False to just print a message.
Simon Glass0d24de92012-01-14 15:12:45 +0000344 cc_fname: Filename of Cc file for per-commit Cc
345 self_only: True to just email to yourself as a test
Doug Anderson6d819922013-03-17 10:31:04 +0000346 in_reply_to: If set we'll pass this to git as --in-reply-to.
347 Should be a message ID that this is in reply to.
Simon Glass0d24de92012-01-14 15:12:45 +0000348
349 Returns:
350 Git command that was/would be run
351
Doug Andersona9700482012-11-26 15:21:40 +0000352 # For the duration of this doctest pretend that we ran patman with ./patman
353 >>> _old_argv0 = sys.argv[0]
354 >>> sys.argv[0] = './patman'
355
Simon Glass0d24de92012-01-14 15:12:45 +0000356 >>> alias = {}
357 >>> alias['fred'] = ['f.bloggs@napier.co.nz']
358 >>> alias['john'] = ['j.bloggs@napier.co.nz']
359 >>> alias['mary'] = ['m.poppins@cloud.net']
360 >>> alias['boys'] = ['fred', ' john']
361 >>> alias['all'] = ['fred ', 'john', ' mary ']
362 >>> alias[os.getenv('USER')] = ['this-is-me@me.com']
363 >>> series = series.Series()
364 >>> series.to = ['fred']
365 >>> series.cc = ['mary']
Simon Glassa1318f72013-03-26 13:09:42 +0000366 >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
367 False, alias)
Simon Glass0d24de92012-01-14 15:12:45 +0000368 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
369"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2'
Simon Glassa1318f72013-03-26 13:09:42 +0000370 >>> EmailPatches(series, None, ['p1'], True, True, 'cc-fname', False, \
371 alias)
Simon Glass0d24de92012-01-14 15:12:45 +0000372 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
373"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" p1'
374 >>> series.cc = ['all']
Simon Glassa1318f72013-03-26 13:09:42 +0000375 >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
376 True, alias)
Simon Glass0d24de92012-01-14 15:12:45 +0000377 'git send-email --annotate --to "this-is-me@me.com" --cc-cmd "./patman \
378--cc-cmd cc-fname" cover p1 p2'
Simon Glassa1318f72013-03-26 13:09:42 +0000379 >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \
380 False, alias)
Simon Glass0d24de92012-01-14 15:12:45 +0000381 'git send-email --annotate --to "f.bloggs@napier.co.nz" --cc \
382"f.bloggs@napier.co.nz" --cc "j.bloggs@napier.co.nz" --cc \
383"m.poppins@cloud.net" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2'
Doug Andersona9700482012-11-26 15:21:40 +0000384
385 # Restore argv[0] since we clobbered it.
386 >>> sys.argv[0] = _old_argv0
Simon Glass0d24de92012-01-14 15:12:45 +0000387 """
Simon Glassa1318f72013-03-26 13:09:42 +0000388 to = BuildEmailList(series.get('to'), '--to', alias, raise_on_error)
Simon Glass0d24de92012-01-14 15:12:45 +0000389 if not to:
390 print ("No recipient, please add something like this to a commit\n"
391 "Series-to: Fred Bloggs <f.blogs@napier.co.nz>")
392 return
Simon Glassa1318f72013-03-26 13:09:42 +0000393 cc = BuildEmailList(series.get('cc'), '--cc', alias, raise_on_error)
Simon Glass0d24de92012-01-14 15:12:45 +0000394 if self_only:
Simon Glassa1318f72013-03-26 13:09:42 +0000395 to = BuildEmailList([os.getenv('USER')], '--to', alias, raise_on_error)
Simon Glass0d24de92012-01-14 15:12:45 +0000396 cc = []
397 cmd = ['git', 'send-email', '--annotate']
Doug Anderson6d819922013-03-17 10:31:04 +0000398 if in_reply_to:
399 cmd.append('--in-reply-to="%s"' % in_reply_to)
400
Simon Glass0d24de92012-01-14 15:12:45 +0000401 cmd += to
402 cmd += cc
403 cmd += ['--cc-cmd', '"%s --cc-cmd %s"' % (sys.argv[0], cc_fname)]
404 if cover_fname:
405 cmd.append(cover_fname)
406 cmd += args
407 str = ' '.join(cmd)
408 if not dry_run:
409 os.system(str)
410 return str
411
412
Simon Glassa1318f72013-03-26 13:09:42 +0000413def LookupEmail(lookup_name, alias=None, raise_on_error=True, level=0):
Simon Glass0d24de92012-01-14 15:12:45 +0000414 """If an email address is an alias, look it up and return the full name
415
416 TODO: Why not just use git's own alias feature?
417
418 Args:
419 lookup_name: Alias or email address to look up
Simon Glassa1318f72013-03-26 13:09:42 +0000420 alias: Dictionary containing aliases (None to use settings default)
421 raise_on_error: True to raise an error when an alias fails to match,
422 False to just print a message.
Simon Glass0d24de92012-01-14 15:12:45 +0000423
424 Returns:
425 tuple:
426 list containing a list of email addresses
427
428 Raises:
429 OSError if a recursive alias reference was found
430 ValueError if an alias was not found
431
432 >>> alias = {}
433 >>> alias['fred'] = ['f.bloggs@napier.co.nz']
434 >>> alias['john'] = ['j.bloggs@napier.co.nz']
435 >>> alias['mary'] = ['m.poppins@cloud.net']
436 >>> alias['boys'] = ['fred', ' john', 'f.bloggs@napier.co.nz']
437 >>> alias['all'] = ['fred ', 'john', ' mary ']
438 >>> alias['loop'] = ['other', 'john', ' mary ']
439 >>> alias['other'] = ['loop', 'john', ' mary ']
440 >>> LookupEmail('mary', alias)
441 ['m.poppins@cloud.net']
442 >>> LookupEmail('arthur.wellesley@howe.ro.uk', alias)
443 ['arthur.wellesley@howe.ro.uk']
444 >>> LookupEmail('boys', alias)
445 ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz']
446 >>> LookupEmail('all', alias)
447 ['f.bloggs@napier.co.nz', 'j.bloggs@napier.co.nz', 'm.poppins@cloud.net']
448 >>> LookupEmail('odd', alias)
449 Traceback (most recent call last):
450 ...
451 ValueError: Alias 'odd' not found
452 >>> LookupEmail('loop', alias)
453 Traceback (most recent call last):
454 ...
455 OSError: Recursive email alias at 'other'
Simon Glassa1318f72013-03-26 13:09:42 +0000456 >>> LookupEmail('odd', alias, raise_on_error=False)
457 \033[1;31mAlias 'odd' not found\033[0m
458 []
459 >>> # In this case the loop part will effectively be ignored.
460 >>> LookupEmail('loop', alias, raise_on_error=False)
461 \033[1;31mRecursive email alias at 'other'\033[0m
462 \033[1;31mRecursive email alias at 'john'\033[0m
463 \033[1;31mRecursive email alias at 'mary'\033[0m
464 ['j.bloggs@napier.co.nz', 'm.poppins@cloud.net']
Simon Glass0d24de92012-01-14 15:12:45 +0000465 """
466 if not alias:
467 alias = settings.alias
468 lookup_name = lookup_name.strip()
469 if '@' in lookup_name: # Perhaps a real email address
470 return [lookup_name]
471
472 lookup_name = lookup_name.lower()
Simon Glassa1318f72013-03-26 13:09:42 +0000473 col = terminal.Color()
Simon Glass0d24de92012-01-14 15:12:45 +0000474
475 out_list = []
Simon Glassa1318f72013-03-26 13:09:42 +0000476 if level > 10:
477 msg = "Recursive email alias at '%s'" % lookup_name
478 if raise_on_error:
479 raise OSError, msg
480 else:
481 print col.Color(col.RED, msg)
482 return out_list
483
Simon Glass0d24de92012-01-14 15:12:45 +0000484 if lookup_name:
485 if not lookup_name in alias:
Simon Glassa1318f72013-03-26 13:09:42 +0000486 msg = "Alias '%s' not found" % lookup_name
487 if raise_on_error:
488 raise ValueError, msg
489 else:
490 print col.Color(col.RED, msg)
491 return out_list
Simon Glass0d24de92012-01-14 15:12:45 +0000492 for item in alias[lookup_name]:
Simon Glassa1318f72013-03-26 13:09:42 +0000493 todo = LookupEmail(item, alias, raise_on_error, level + 1)
Simon Glass0d24de92012-01-14 15:12:45 +0000494 for new_item in todo:
495 if not new_item in out_list:
496 out_list.append(new_item)
497
498 #print "No match for alias '%s'" % lookup_name
499 return out_list
500
501def GetTopLevel():
502 """Return name of top-level directory for this git repo.
503
504 Returns:
505 Full path to git top-level directory
506
507 This test makes sure that we are running tests in the right subdir
508
Doug Andersona9700482012-11-26 15:21:40 +0000509 >>> os.path.realpath(os.path.dirname(__file__)) == \
510 os.path.join(GetTopLevel(), 'tools', 'patman')
Simon Glass0d24de92012-01-14 15:12:45 +0000511 True
512 """
513 return command.OutputOneLine('git', 'rev-parse', '--show-toplevel')
514
515def GetAliasFile():
516 """Gets the name of the git alias file.
517
518 Returns:
519 Filename of git alias file, or None if none
520 """
Simon Glassdc191502012-12-15 10:42:05 +0000521 fname = command.OutputOneLine('git', 'config', 'sendemail.aliasesfile',
522 raise_on_error=False)
Simon Glass0d24de92012-01-14 15:12:45 +0000523 if fname:
524 fname = os.path.join(GetTopLevel(), fname.strip())
525 return fname
526
Vikram Narayanan87d65552012-05-23 09:01:06 +0000527def GetDefaultUserName():
528 """Gets the user.name from .gitconfig file.
529
530 Returns:
531 User name found in .gitconfig file, or None if none
532 """
533 uname = command.OutputOneLine('git', 'config', '--global', 'user.name')
534 return uname
535
536def GetDefaultUserEmail():
537 """Gets the user.email from the global .gitconfig file.
538
539 Returns:
540 User's email found in .gitconfig file, or None if none
541 """
542 uemail = command.OutputOneLine('git', 'config', '--global', 'user.email')
543 return uemail
544
Simon Glass0d24de92012-01-14 15:12:45 +0000545def Setup():
546 """Set up git utils, by reading the alias files."""
Simon Glass0d24de92012-01-14 15:12:45 +0000547 # Check for a git alias file also
548 alias_fname = GetAliasFile()
549 if alias_fname:
550 settings.ReadGitAliases(alias_fname)
551
Simon Glass5f6a1c42012-12-15 10:42:07 +0000552def GetHead():
553 """Get the hash of the current HEAD
554
555 Returns:
556 Hash of HEAD
557 """
558 return command.OutputOneLine('git', 'show', '-s', '--pretty=format:%H')
559
Simon Glass0d24de92012-01-14 15:12:45 +0000560if __name__ == "__main__":
561 import doctest
562
563 doctest.testmod()