diff --git a/git_cl.py b/git_cl.py index 849e12ea4..c4c3951b2 100755 --- a/git_cl.py +++ b/git_cl.py @@ -1402,6 +1402,22 @@ class Changelist(object): return '\n'.join([wrapper.fill(line) for line in lines]) return self.description + def GetDescriptionFooters(self): + """Returns (non_footer_lines, footers) for the commit message. + + Returns: + non_footer_lines (list(str)) - Simple list of description lines without + any footer. The lines do not contain newlines, nor does the list contain + the empty line between the message and the footers. + footers (list(tuple(KEY, VALUE))) - List of parsed footers, e.g. + [("Change-Id", "Ideadbeef...."), ...] + """ + raw_description = self.GetDescription() + msg_lines, _, footers = git_footers.split_footers(raw_description) + if footers: + msg_lines = msg_lines[:len(msg_lines)-1] + return msg_lines, footers + def GetPatchset(self): """Returns the patchset number as a int or None if not set.""" if self.patchset is None and not self.lookedup_patchset: @@ -1497,6 +1513,29 @@ class Changelist(object): self.description = description self.has_description = True + def UpdateDescriptionFooters(self, description_lines, footers, force=False): + """Sets the description for this CL remotely. + + You can get description_lines and footers with GetDescriptionFooters. + + Args: + description_lines (list(str)) - List of CL description lines without + newline characters. + footers (list(tuple(KEY, VALUE))) - List of footers, as returned by + GetDescriptionFooters. Key must conform to the git footers format (i.e. + `List-Of-Tokens`). It will be case-normalized so that each token is + title-cased. + """ + new_description = '\n'.join(description_lines) + if footers: + new_description += '\n' + for k, v in footers: + foot = '%s: %s' % (git_footers.normalize_name(k), v) + if not git_footers.FOOTER_PATTERN.match(foot): + raise ValueError('Invalid footer %r' % foot) + new_description += foot + '\n' + self.UpdateDescription(new_description, force) + def RunHook(self, committing, may_prompt, verbose, change): """Calls sys.exit() if the hook fails; returns a HookResults otherwise.""" try: diff --git a/tests/git_cl_test.py b/tests/git_cl_test.py index df5fd8105..f5cbb7213 100755 --- a/tests/git_cl_test.py +++ b/tests/git_cl_test.py @@ -185,6 +185,40 @@ class TestGitClBasic(unittest.TestCase): self.assertEquals(cl.GetDescription(force=True), 'y') self.assertEquals(cl.GetDescription(), 'y') + def test_description_footers(self): + cl = git_cl.Changelist(issue=1, codereview='gerrit', + codereview_host='host') + cl.description = '\n'.join([ + 'This is some message', + '', + 'It has some lines', + 'and, also', + '', + 'Some: Really', + 'Awesome: Footers', + ]) + cl.has_description = True + cl._codereview_impl.UpdateDescriptionRemote = lambda *a, **kw: 'y' + msg, footers = cl.GetDescriptionFooters() + self.assertEquals( + msg, ['This is some message', '', 'It has some lines', 'and, also']) + self.assertEquals(footers, [('Some', 'Really'), ('Awesome', 'Footers')]) + + msg.append('wut') + footers.append(('gnarly-dude', 'beans')) + cl.UpdateDescriptionFooters(msg, footers) + self.assertEquals(cl.GetDescription().splitlines(), [ + 'This is some message', + '', + 'It has some lines', + 'and, also', + 'wut' + '', + 'Some: Really', + 'Awesome: Footers', + 'Gnarly-Dude: beans', + ]) + def _test_ParseIssueUrl(self, func, url, issue, patchset, hostname, fail): parsed = urlparse.urlparse(url) result = func(parsed)