Author: dsahlberg
Date: Sat Mar 1 21:15:27 2025
New Revision: 1924120
URL: http://svn.apache.org/viewvc?rev=1924120&view=rev
Log:
Parse additional subheaders and recreate the StatusEntry from its attributes
instead of just outputting the original entry.
This adds some normalization of the recreated entry:
* List of revisions is a single line. Parser can accept line breaks.
* The branch name is listed on a separate line after the Branch subheader
and without ^/subversion/branches/ path. Parser can accept branch name
on the same line and with full path.
* No spaces on the line after a status entry. Parser can accept any number of
spaces.
* tools/dist/backport/status.py
(StatusEntry): New attributes justification_str, depends_str and notes_str
(StatusEntry.__init__): Parse the additional attributes and keep track of
some additional state required to recreate a faithful StatusEntry.
(StatusEntry.unparse): Remove obsolete comment (should have been moved to
__str__ in r1924119).
(StatusEntry.__str__): Create the StatusEntry from the individual attributes.
(Test_StatusEntry.test___init__): Modify two unittest where the normalization
caused failures. Also check the last line doesn't contain spaces.
Modified:
subversion/trunk/tools/dist/backport/status.py
Modified: subversion/trunk/tools/dist/backport/status.py
URL: http://svn.apache.org/viewvc/subversion/trunk/tools/dist/backport/status.py?rev=1924120&r1=1924119&r2=1924120&view=diff
==============================================================================
--- subversion/trunk/tools/dist/backport/status.py (original)
+++ subversion/trunk/tools/dist/backport/status.py Sat Mar 1 21:15:27 2025
@@ -336,8 +336,11 @@ class StatusEntry:
branch - the backport branch's basename, or None.
revisions - the revisions to nominated, as iterable of int.
logsummary - the text before the justification, as an array of lines.
+ justification_str - the justification, as an array of lines. An unparsed string.
depends - true if a "Depends:" entry was found, False otherwise.
+ depends_str - everything after the "Depends:" subheader. An unparsed string.
accept - the value to pass to 'svn merge --accept=%s', or None.
+ notes_str - everything after the "Notes:" subheader. An unparsed string.
votes_str - everything after the "Votes:" subheader. An unparsed string.
"""
@@ -348,10 +351,14 @@ class StatusEntry:
STATUS_FILE is the StatusFile object containing this entry, if any.
"""
self.branch = None
+ self.branch_on_first_line = False
self.revisions = []
self.logsummary = []
+ self.justification_str = None
self.depends = False
+ self.depends_str = None
self.accept = None
+ self.notes_str = None
self.votes_str = None
self.status_file = status_file
@@ -366,8 +373,8 @@ class StatusEntry:
match = _re_entry_indentation.match(lines[0])
if not match:
raise ParseException("Entry found with no ' * ' line")
- indentation = len(match.group(1))
- lines = (line[indentation:] for line in lines)
+ self.indentation = len(match.group(1))
+ lines = (line[self.indentation:] for line in lines)
lines = (line.rstrip() for line in lines)
# Consume the generator.
@@ -378,6 +385,7 @@ class StatusEntry:
if match:
# Parse whichever group matched.
self.branch = self.parse_branch(match.group(1) or match.group(2))
+ self.branch_on_first_line = True
else:
while _re_revisions_line.match(lines[0]):
self.revisions.extend(map(int, re.compile(r'(\d+)').findall(lines[0])))
@@ -403,14 +411,26 @@ class StatusEntry:
else:
self.votes_str = None
- # depends, branch, notes
+ # depends, justification, branch, notes
while lines:
if lines[0].strip().startswith('Depends:'):
self.depends = True
+ self.depends_str = lines[0].strip()[8:].strip()
lines = lines[1:]
continue
+ if lines[0].strip().startswith('Justification:'):
+ self.justification_str = lines[0].strip().split(':', 1)[1] + "\n"
+ lines = lines[1:]
+
+ # Consume the indented body of the "Justification" field.
+ while lines and not lines[0][0].isalnum():
+ self.justification_str += lines[0] + "\n"
+ lines = lines[1:]
+
+ continue
+
if lines[0].strip().startswith('Branch:'):
maybe_value = lines[0].strip().split(':', 1)[1]
if maybe_value.strip():
@@ -427,16 +447,16 @@ class StatusEntry:
continue
if lines[0].strip().startswith('Notes:'):
- notes = lines[0].strip().split(':', 1)[1] + "\n"
+ self.notes_str = lines[0].strip().split(':', 1)[1] + "\n"
lines = lines[1:]
# Consume the indented body of the "Notes" field.
while lines and not lines[0][0].isalnum():
- notes += lines[0] + "\n"
+ self.notes_str += lines[0] + "\n"
lines = lines[1:]
# Look for possible --accept directives.
- matches = re.compile(r'--accept[ =]([a-z-]+)').findall(notes)
+ matches = re.compile(r'--accept[ =]([a-z-]+)').findall(self.notes_str)
if len(matches) > 1:
raise ParseException("Too many --accept values at %s" % (self,))
elif len(matches) == 1:
@@ -527,19 +547,42 @@ class StatusEntry:
def unparse(self, stream):
"Write this entry to STREAM, an open file-like object."
- # For now, this is simple.. until we add interactive editing.
stream.write(self.__str__())
def __str__(self):
- s = self.raw
+ indent = ''.ljust(self.indentation-2)
+ s = indent + '* '
+ if (len(self.revisions) > 0):
+ s += 'r' + ', r'.join(map(str, self.revisions)) + \
+ ('\n' + indent + ' ').join([""] + self.logsummary) + '\n'
+ else:
+ s += ('\n' + indent + ' ').join(self.logsummary) + '\n'
+ if self.justification_str is not None:
+ s += indent + ' Justification:' + \
+ ('\n' + indent + ' ').join(self.justification_str.split("\n")[:-1]) + \
+ '\n'
+ if self.branch is not None and not self.branch_on_first_line:
+ s += indent + ' Branch:' + \
+ '\n' + indent + ' ' + self.branch + \
+ '\n'
+ if self.depends:
+ s += indent + ' Depends: ' + self.depends_str + '\n'
+ if self.notes_str is not None:
+ s += indent + ' Notes:' + \
+ ('\n' + indent + ' ').join(self.notes_str.split("\n")[:-1]) + \
+ '\n'
+ if self.votes_str is not None:
+ s += indent + ' Votes:' + \
+ ('\n' + indent + ' ').join([""] + self.votes_str.split("\n")[:-1]) + "\n"
return s
class Test_StatusEntry(unittest.TestCase):
def test___init__(self):
"Test the entry parser"
- # All these entries actually have a "four spaces" line as their last line,
- # but the parser doesn't care.
+ # An entry may have spaces on its last line, the parser doesn't care.
+ # However unparse() will not replicate this, causing a failure on
+ # assertEqual(entry.unparse())
s = """\
* r42, r43,
@@ -550,8 +593,17 @@ class Test_StatusEntry(unittest.TestCase
Votes:
+1: jrandom
"""
+ # Revision list will be normalized to one line
+ sExpected = """\
+ * r42, r43, r44
+ This is the logsummary.
+ Branch:
+ 1.8.x-rfourty-two
+ Votes:
+ +1: jrandom
+"""
entry = StatusEntry(s)
- self.assertEqual(entry.__str__(), s)
+ self.assertEqual(entry.__str__(), sExpected)
self.assertEqual(entry.branch, "1.8.x-rfourty-two")
self.assertEqual(entry.revisions, [42, 43, 44])
self.assertEqual(entry.logsummary, ["This is the logsummary."])
@@ -574,7 +626,7 @@ class Test_StatusEntry(unittest.TestCase
Votes:
+1: jrandom
-1: jconstant
- """
+"""
entry = StatusEntry(s)
self.assertEqual(entry.__str__(), s)
self.assertIsNone(entry.branch)
@@ -597,7 +649,7 @@ class Test_StatusEntry(unittest.TestCase
Votes:
+1: jrandom
-1 (see <message-id>): jconstant
- """
+"""
entry = StatusEntry(s)
self.assertEqual(entry.__str__(), s)
self.assertEqual(entry.branch, "1.8.x-fixes")
@@ -611,8 +663,17 @@ class Test_StatusEntry(unittest.TestCase
Votes:
+1: jrandom
"""
+ # Normalizes branch (without path) to separate line
+ sExpected = """\
+ * r42
+ This is the logsummary.
+ Branch:
+ on-the-same-line
+ Votes:
+ +1: jrandom
+"""
entry = StatusEntry(s)
- self.assertEqual(entry.__str__(), s)
+ self.assertEqual(entry.__str__(), sExpected)
self.assertEqual(entry.branch, "on-the-same-line")
self.assertEqual(entry.revisions, [42])
@@ -624,7 +685,7 @@ class Test_StatusEntry(unittest.TestCase
This is the logsummary.
Votes:
+1: jrandom
- """
+"""
entry = StatusEntry(s)
self.assertEqual(entry.__str__(), s)
self.assertEqual(entry.branch, "1.8.x-fixes")
@@ -667,7 +728,7 @@ class Test_StatusEntry(unittest.TestCase
Fixes output that scripts depend on.
Votes:
+1: jrandom
- """
+"""
entry = StatusEntry(s)
self.assertEqual(entry.__str__(), s)
self.assertEqual(entry.revisions, [42])
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.