def MakeCcFile(self, process_tags, cover_fname, raise_on_error, add_maintainers, limit): """Make a cc file for us to use for per-commit Cc automation Also stores in self._generated_cc to make ShowActions() faster. Args: process_tags: Process tags as if they were aliases cover_fname: If non-None the name of the cover letter. raise_on_error: True to raise an error when an alias fails to match, False to just print a message. add_maintainers: Either: True/False to call the get_maintainers to CC maintainers List of maintainers to include (for testing) limit: Limit the length of the Cc list (None if no limit) Return: Filename of temp file created """ col = terminal.Color() # Look for commit tags (of the form 'xxx:' at the start of the subject) fname = '/tmp/patman.%d' % os.getpid() fd = open(fname, 'w', encoding='utf-8') all_ccs = [] for commit in self.commits: cc = [] if process_tags: cc += gitutil.BuildEmailList(commit.tags, raise_on_error=raise_on_error) cc += gitutil.BuildEmailList(commit.cc_list, raise_on_error=raise_on_error) if type(add_maintainers) == type(cc): cc += add_maintainers elif add_maintainers: dir_list = [os.path.join(gitutil.GetTopLevel(), 'scripts')] cc += get_maintainer.GetMaintainer(dir_list, commit.patch) for x in set(cc) & set(settings.bounces): print(col.Color(col.YELLOW, 'Skipping "%s"' % x)) cc = set(cc) - set(settings.bounces) cc = [tools.FromUnicode(m) for m in cc] if limit is not None: cc = cc[:limit] all_ccs += cc print(commit.patch, '\0'.join(sorted(set(cc))), file=fd) self._generated_cc[commit.patch] = cc if cover_fname: cover_cc = gitutil.BuildEmailList(self.get('cover_cc', '')) cover_cc = [tools.FromUnicode(m) for m in cover_cc] cover_cc = list(set(cover_cc + all_ccs)) if limit is not None: cover_cc = cover_cc[:limit] cc_list = '\0'.join([tools.ToUnicode(x) for x in sorted(cover_cc)]) print(cover_fname, cc_list, file=fd) fd.close() return fname
def _AddEntries(areas, entry): entries = entry.GetEntries() tout.Debug("fmap: Add entry '%s' type '%s' (%s subentries)" % (entry.GetPath(), entry.etype, ToHexSize(entries))) if entries and entry.etype != 'cbfs': for subentry in entries.values(): _AddEntries(areas, subentry) else: pos = entry.image_pos if pos is not None: pos -= entry.section.GetRootSkipAtStart() areas.append( fmap_util.FmapArea(pos or 0, entry.size or 0, tools.FromUnicode(entry.name), 0))
def BuildEmailList(in_list, tag=None, alias=None, raise_on_error=True): """Build a list of email addresses based on an input list. Takes a list of email addresses and aliases, and turns this into a list of only email address, by resolving any aliases that are present. If the tag is given, then each email address is prepended with this tag and a space. If the tag starts with a minus sign (indicating a command line parameter) then the email address is quoted. Args: in_list: List of aliases/email addresses tag: Text to put before each address alias: Alias dictionary raise_on_error: True to raise an error when an alias fails to match, False to just print a message. Returns: List of email addresses >>> alias = {} >>> alias['fred'] = ['*****@*****.**'] >>> alias['john'] = ['*****@*****.**'] >>> alias['mary'] = ['Mary Poppins <*****@*****.**>'] >>> alias['boys'] = ['fred', ' john'] >>> alias['all'] = ['fred ', 'john', ' mary '] >>> BuildEmailList(['john', 'mary'], None, alias) ['*****@*****.**', 'Mary Poppins <*****@*****.**>'] >>> BuildEmailList(['john', 'mary'], '--to', alias) ['--to "*****@*****.**"', \ '--to "Mary Poppins <*****@*****.**>"'] >>> BuildEmailList(['john', 'mary'], 'Cc', alias) ['Cc [email protected]', 'Cc Mary Poppins <*****@*****.**>'] """ quote = '"' if tag and tag[0] == '-' else '' raw = [] for item in in_list: raw += LookupEmail(item, alias, raise_on_error=raise_on_error) result = [] for item in raw: item = tools.FromUnicode(item) if not item in result: result.append(item) if tag: return ['%s %s%s%s' % (tag, quote, email, quote) for email in result] return result
def EncodeFmap(image_size, name, areas): """Create a new FMAP from a list of areas Args: image_size: Size of image, to put in the header name: Name of image, to put in the header areas: List of FmapArea objects Returns: String containing the FMAP created """ def _FormatBlob(fmt, names, obj): params = [getattr(obj, name) for name in names] ConvertName(names, params) return struct.pack(fmt, *params) values = FmapHeader(FMAP_SIGNATURE, 1, 0, 0, image_size, tools.FromUnicode(name), len(areas)) blob = _FormatBlob(FMAP_HEADER_FORMAT, FMAP_HEADER_NAMES, values) for area in areas: blob += _FormatBlob(FMAP_AREA_FORMAT, FMAP_AREA_NAMES, area) return blob
def EmailPatches(series, cover_fname, args, dry_run, raise_on_error, cc_fname, self_only=False, alias=None, in_reply_to=None, thread=False, smtp_server=None): """Email a patch series. Args: series: Series object containing destination info cover_fname: filename of cover letter args: list of filenames of patch files dry_run: Just return the command that would be run raise_on_error: True to raise an error when an alias fails to match, False to just print a message. cc_fname: Filename of Cc file for per-commit Cc self_only: True to just email to yourself as a test in_reply_to: If set we'll pass this to git as --in-reply-to. Should be a message ID that this is in reply to. thread: True to add --thread to git send-email (make all patches reply to cover-letter or first patch in series) smtp_server: SMTP server to use to send patches Returns: Git command that was/would be run # For the duration of this doctest pretend that we ran patman with ./patman >>> _old_argv0 = sys.argv[0] >>> sys.argv[0] = './patman' >>> alias = {} >>> alias['fred'] = ['*****@*****.**'] >>> alias['john'] = ['*****@*****.**'] >>> alias['mary'] = ['*****@*****.**'] >>> alias['boys'] = ['fred', ' john'] >>> alias['all'] = ['fred ', 'john', ' mary '] >>> alias[os.getenv('USER')] = ['*****@*****.**'] >>> series = {} >>> series['to'] = ['fred'] >>> series['cc'] = ['mary'] >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ False, alias) 'git send-email --annotate --to "*****@*****.**" --cc \ "*****@*****.**" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2' >>> EmailPatches(series, None, ['p1'], True, True, 'cc-fname', False, \ alias) 'git send-email --annotate --to "*****@*****.**" --cc \ "*****@*****.**" --cc-cmd "./patman --cc-cmd cc-fname" p1' >>> series['cc'] = ['all'] >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ True, alias) 'git send-email --annotate --to "*****@*****.**" --cc-cmd "./patman \ --cc-cmd cc-fname" cover p1 p2' >>> EmailPatches(series, 'cover', ['p1', 'p2'], True, True, 'cc-fname', \ False, alias) 'git send-email --annotate --to "*****@*****.**" --cc \ "*****@*****.**" --cc "*****@*****.**" --cc \ "*****@*****.**" --cc-cmd "./patman --cc-cmd cc-fname" cover p1 p2' # Restore argv[0] since we clobbered it. >>> sys.argv[0] = _old_argv0 """ to = BuildEmailList(series.get('to'), '--to', alias, raise_on_error) if not to: git_config_to = command.Output('git', 'config', 'sendemail.to', raise_on_error=False) if not git_config_to: print("No recipient.\n" "Please add something like this to a commit\n" "Series-to: Fred Bloggs <*****@*****.**>\n" "Or do something like this\n" "git config sendemail.to [email protected]") return cc = BuildEmailList(list(set(series.get('cc')) - set(series.get('to'))), '--cc', alias, raise_on_error) if self_only: to = BuildEmailList([os.getenv('USER')], '--to', alias, raise_on_error) cc = [] cmd = ['git', 'send-email', '--annotate'] if smtp_server: cmd.append('--smtp-server=%s' % smtp_server) if in_reply_to: cmd.append('--in-reply-to="%s"' % tools.FromUnicode(in_reply_to)) if thread: cmd.append('--thread') cmd += to cmd += cc cmd += ['--cc-cmd', '"%s --cc-cmd %s"' % (sys.argv[0], cc_fname)] if cover_fname: cmd.append(cover_fname) cmd += args cmdstr = ' '.join(cmd) if not dry_run: os.system(cmdstr) return cmdstr
def testBasic(self): """Tests the basic flow of patman This creates a series from some hard-coded patches build from a simple tree with the following metadata in the top commit: Series-to: u-boot Series-prefix: RFC Series-cc: Stefan Brüns <*****@*****.**> Cover-letter-cc: Lord Mëlchett <*****@*****.**> Series-version: 3 Patch-cc: fred Series-process-log: sort, uniq Series-changes: 4 - Some changes - Multi line change Commit-changes: 2 - Changes only for this commit Cover-changes: 4 - Some notes for the cover letter Cover-letter: test: A test patch series This is a test of how the cover letter works END and this in the first commit: Commit-changes: 2 - second revision change Series-notes: some notes about some things from the first commit END Commit-notes: Some notes about the first commit END with the following commands: git log -n2 --reverse >/path/to/tools/patman/test/test01.txt git format-patch --subject-prefix RFC --cover-letter HEAD~2 mv 00* /path/to/tools/patman/test It checks these aspects: - git log can be processed by patchstream - emailing patches uses the correct command - CC file has information on each commit - cover letter has the expected text and subject - each patch has the correct subject - dry-run information prints out correctly - unicode is handled correctly - Series-to, Series-cc, Series-prefix, Cover-letter - Cover-letter-cc, Series-version, Series-changes, Series-notes - Commit-notes """ process_tags = True ignore_bad_tags = True stefan = b'Stefan Br\xc3\xbcns <*****@*****.**>'.decode( 'utf-8') rick = 'Richard III <*****@*****.**>' mel = b'Lord M\xc3\xablchett <*****@*****.**>'.decode('utf-8') add_maintainers = [stefan, rick] dry_run = True in_reply_to = mel count = 2 settings.alias = { 'fdt': ['simon'], 'u-boot': ['*****@*****.**'], 'simon': [self.leb], 'fred': [self.fred], } text = self._get_text('test01.txt') series = patchstream.get_metadata_for_test(text) cover_fname, args = self._create_patches_for_test(series) with capture_sys_output() as out: patchstream.fix_patches(series, args) if cover_fname and series.get('cover'): patchstream.insert_cover_letter(cover_fname, series, count) series.DoChecks() cc_file = series.MakeCcFile(process_tags, cover_fname, not ignore_bad_tags, add_maintainers, None) cmd = gitutil.EmailPatches(series, cover_fname, args, dry_run, not ignore_bad_tags, cc_file, in_reply_to=in_reply_to, thread=None) series.ShowActions(args, cmd, process_tags) cc_lines = open(cc_file, encoding='utf-8').read().splitlines() os.remove(cc_file) lines = iter(out[0].getvalue().splitlines()) self.assertEqual('Cleaned %s patches' % len(series.commits), next(lines)) self.assertEqual('Change log missing for v2', next(lines)) self.assertEqual('Change log missing for v3', next(lines)) self.assertEqual('Change log for unknown version v4', next(lines)) self.assertEqual("Alias 'pci' not found", next(lines)) self.assertIn('Dry run', next(lines)) self.assertEqual('', next(lines)) self.assertIn('Send a total of %d patches' % count, next(lines)) prev = next(lines) for i, commit in enumerate(series.commits): self.assertEqual(' %s' % args[i], prev) while True: prev = next(lines) if 'Cc:' not in prev: break self.assertEqual('To: [email protected]', prev) self.assertEqual('Cc: %s' % tools.FromUnicode(stefan), next(lines)) self.assertEqual('Version: 3', next(lines)) self.assertEqual('Prefix:\t RFC', next(lines)) self.assertEqual('Cover: 4 lines', next(lines)) self.assertEqual(' Cc: %s' % self.fred, next(lines)) self.assertEqual(' Cc: %s' % tools.FromUnicode(self.leb), next(lines)) self.assertEqual(' Cc: %s' % tools.FromUnicode(mel), next(lines)) self.assertEqual(' Cc: %s' % rick, next(lines)) expected = ('Git command: git send-email --annotate ' '--in-reply-to="%s" --to "*****@*****.**" ' '--cc "%s" --cc-cmd "%s --cc-cmd %s" %s %s' % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname, ' '.join(args))) self.assertEqual(expected, tools.ToUnicode(next(lines))) self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)), tools.ToUnicode(cc_lines[0])) self.assertEqual( '%s %s\0%s\0%s\0%s' % (args[1], self.fred, self.leb, rick, stefan), tools.ToUnicode(cc_lines[1])) expected = ''' This is a test of how the cover letter works some notes about some things from the first commit Changes in v4: - Multi line change - Some changes - Some notes for the cover letter Simon Glass (2): pci: Correct cast for sandbox fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base() cmd/pci.c | 3 ++- fs/fat/fat.c | 1 + lib/efi_loader/efi_memory.c | 1 + lib/fdtdec.c | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) --\x20 2.7.4 ''' lines = open(cover_fname, encoding='utf-8').read().splitlines() self.assertEqual( 'Subject: [RFC PATCH v3 0/2] test: A test patch series', lines[3]) self.assertEqual(expected.splitlines(), lines[7:]) for i, fname in enumerate(args): lines = open(fname, encoding='utf-8').read().splitlines() subject = [line for line in lines if line.startswith('Subject')] self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count), subject[0][:18]) # Check that we got our commit notes start = 0 expected = '' if i == 0: start = 17 expected = '''--- Some notes about the first commit (no changes since v2) Changes in v2: - second revision change''' elif i == 1: start = 17 expected = '''--- Changes in v4: - Multi line change - Some changes Changes in v2: - Changes only for this commit''' if expected: expected = expected.splitlines() self.assertEqual(expected, lines[start:(start + len(expected))])
def testBasic(self): """Tests the basic flow of patman This creates a series from some hard-coded patches build from a simple tree with the following metadata in the top commit: Series-to: u-boot Series-prefix: RFC Series-cc: Stefan Brüns <*****@*****.**> Cover-letter-cc: Lord Mëlchett <*****@*****.**> Series-version: 2 Series-changes: 4 - Some changes Cover-letter: test: A test patch series This is a test of how the cover leter works END and this in the first commit: Series-notes: some notes about some things from the first commit END Commit-notes: Some notes about the first commit END with the following commands: git log -n2 --reverse >/path/to/tools/patman/test/test01.txt git format-patch --subject-prefix RFC --cover-letter HEAD~2 mv 00* /path/to/tools/patman/test It checks these aspects: - git log can be processed by patchstream - emailing patches uses the correct command - CC file has information on each commit - cover letter has the expected text and subject - each patch has the correct subject - dry-run information prints out correctly - unicode is handled correctly - Series-to, Series-cc, Series-prefix, Cover-letter - Cover-letter-cc, Series-version, Series-changes, Series-notes - Commit-notes """ process_tags = True ignore_bad_tags = True stefan = b'Stefan Br\xc3\xbcns <*****@*****.**>'.decode( 'utf-8') rick = 'Richard III <*****@*****.**>' mel = b'Lord M\xc3\xablchett <*****@*****.**>'.decode('utf-8') ed = b'Lond Edmund Blackadd\xc3\xabr <*****@*****.**'.decode( 'utf-8') fred = 'Fred Bloggs <*****@*****.**>' add_maintainers = [stefan, rick] dry_run = True in_reply_to = mel count = 2 settings.alias = { 'fdt': ['simon'], 'u-boot': ['*****@*****.**'], 'simon': [ed], 'fred': [fred], } text = self.GetText('test01.txt') series = patchstream.GetMetaDataForTest(text) cover_fname, args = self.CreatePatchesForTest(series) with capture() as out: patchstream.FixPatches(series, args) if cover_fname and series.get('cover'): patchstream.InsertCoverLetter(cover_fname, series, count) series.DoChecks() cc_file = series.MakeCcFile(process_tags, cover_fname, not ignore_bad_tags, add_maintainers, None) cmd = gitutil.EmailPatches(series, cover_fname, args, dry_run, not ignore_bad_tags, cc_file, in_reply_to=in_reply_to, thread=None) series.ShowActions(args, cmd, process_tags) cc_lines = open(cc_file, encoding='utf-8').read().splitlines() os.remove(cc_file) lines = out[0].splitlines() self.assertEqual('Cleaned %s patches' % len(series.commits), lines[0]) self.assertEqual('Change log missing for v2', lines[1]) self.assertEqual('Change log missing for v3', lines[2]) self.assertEqual('Change log for unknown version v4', lines[3]) self.assertEqual("Alias 'pci' not found", lines[4]) self.assertIn('Dry run', lines[5]) self.assertIn('Send a total of %d patches' % count, lines[7]) line = 8 for i, commit in enumerate(series.commits): self.assertEqual(' %s' % args[i], lines[line + 0]) line += 1 while 'Cc:' in lines[line]: line += 1 self.assertEqual('To: [email protected]', lines[line]) self.assertEqual('Cc: %s' % tools.FromUnicode(stefan), lines[line + 1]) self.assertEqual('Version: 3', lines[line + 2]) self.assertEqual('Prefix:\t RFC', lines[line + 3]) self.assertEqual('Cover: 4 lines', lines[line + 4]) line += 5 self.assertEqual(' Cc: %s' % fred, lines[line + 0]) self.assertEqual(' Cc: %s' % tools.FromUnicode(ed), lines[line + 1]) self.assertEqual(' Cc: %s' % tools.FromUnicode(mel), lines[line + 2]) self.assertEqual(' Cc: %s' % rick, lines[line + 3]) expected = ('Git command: git send-email --annotate ' '--in-reply-to="%s" --to "*****@*****.**" ' '--cc "%s" --cc-cmd "%s --cc-cmd %s" %s %s' % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname, ' '.join(args))) line += 4 self.assertEqual(expected, tools.ToUnicode(lines[line])) self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)), tools.ToUnicode(cc_lines[0])) self.assertEqual( ('%s %s\0%s\0%s\0%s' % (args[1], fred, ed, rick, stefan)), tools.ToUnicode(cc_lines[1])) expected = ''' This is a test of how the cover leter works some notes about some things from the first commit Changes in v4: - Some changes Simon Glass (2): pci: Correct cast for sandbox fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base() cmd/pci.c | 3 ++- fs/fat/fat.c | 1 + lib/efi_loader/efi_memory.c | 1 + lib/fdtdec.c | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) --\x20 2.7.4 ''' lines = open(cover_fname, encoding='utf-8').read().splitlines() self.assertEqual( 'Subject: [RFC PATCH v3 0/2] test: A test patch series', lines[3]) self.assertEqual(expected.splitlines(), lines[7:]) for i, fname in enumerate(args): lines = open(fname, encoding='utf-8').read().splitlines() subject = [line for line in lines if line.startswith('Subject')] self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count), subject[0][:18]) if i == 0: # Check that we got our commit notes self.assertEqual('---', lines[17]) self.assertEqual('Some notes about', lines[18]) self.assertEqual('the first commit', lines[19])