import string, sys, re, os, shutil
import svntest
from svntest import SVNAnyOutput
max_revision = 0
msg_separator = '------------------------------------' \
+ '------------------------------------\n'
Skip = svntest.testcase.Skip
XFail = svntest.testcase.XFail
Item = svntest.wc.StateItem
def guarantee_repos_and_wc(sbox):
"Make a repos and wc, commit max_revision revs."
global max_revision
sbox.build()
wc_path = sbox.wc_dir
msg_file=os.path.join (sbox.repo_dir, 'log-msg')
msg_file=os.path.abspath (msg_file)
was_cwd = os.getcwd ()
os.chdir (wc_path)
iota_path = os.path.join ('iota')
mu_path = os.path.join ('A', 'mu')
B_path = os.path.join ('A', 'B')
omega_path = os.path.join ('A', 'D', 'H', 'omega')
pi_path = os.path.join ('A', 'D', 'G', 'pi')
rho_path = os.path.join ('A', 'D', 'G', 'rho')
alpha_path = os.path.join ('A', 'B', 'E', 'alpha')
beta_path = os.path.join ('A', 'B', 'E', 'beta')
psi_path = os.path.join ('A', 'D', 'H', 'psi')
epsilon_path = os.path.join ('A', 'C', 'epsilon')
msg=""" Log message for revision 2
but with multiple lines
to test the code"""
log_file=open (msg_file, 'w')
log_file.write (msg)
log_file.close ()
svntest.main.file_append (iota_path, "2")
svntest.main.run_svn (None, 'ci', '-F', msg_file)
svntest.main.run_svn (None, 'up')
svntest.main.file_append (omega_path, "3")
svntest.main.file_append (pi_path, "3")
svntest.main.file_append (rho_path, "3")
svntest.main.file_append (alpha_path, "3")
svntest.main.run_svn (None, 'ci', '-m', "Log message for revision 3")
svntest.main.run_svn (None, 'up')
msg=""" Log message for revision 4
but with multiple lines
to test the code"""
log_file=open (msg_file, 'w')
log_file.write (msg)
log_file.close ()
svntest.main.file_append (iota_path, "4")
svntest.main.file_append (epsilon_path, "4")
svntest.main.run_svn (None, 'add', epsilon_path)
svntest.main.run_svn (None, 'ci', '-F', msg_file)
svntest.main.run_svn (None, 'up')
svntest.main.file_append (epsilon_path, "5")
svntest.main.run_svn (None, 'rm', rho_path)
svntest.main.run_svn (None, 'ci', '-m', "Log message for revision 5")
svntest.main.run_svn (None, 'up')
msg=""" Log message for revision 6
but with multiple lines
to test the code"""
log_file=open (msg_file, 'w')
log_file.write (msg)
log_file.close ()
svntest.main.run_svn (None, 'ps', 'blue', 'azul', B_path)
svntest.main.file_append (psi_path, "6")
svntest.main.run_svn (None, 'ci', '-F', msg_file)
svntest.main.run_svn (None, 'up')
svntest.main.file_append (mu_path, "7")
svntest.main.run_svn (None, 'ps', 'red', 'burgundy', mu_path)
svntest.main.run_svn (None, 'ci', '-m', "Log message for revision 7")
svntest.main.run_svn (None, 'up')
msg=""" Log message for revision 8
but with multiple lines
to test the code"""
log_file=open (msg_file, 'w')
log_file.write (msg)
log_file.close ()
svntest.main.file_append (iota_path, "8")
svntest.main.file_append (rho_path, "8")
svntest.main.run_svn (None, 'add', rho_path)
svntest.main.run_svn (None, 'ci', '-F', msg_file)
svntest.main.run_svn (None, 'up')
svntest.main.file_append (beta_path, "9")
svntest.main.run_svn (None, 'rm', alpha_path)
svntest.main.run_svn (None, 'ci', '-m', "Log message for revision 9")
svntest.main.run_svn (None, 'up')
max_revision = 9
os.chdir (was_cwd)
expected_status = svntest.actions.get_virginal_state(wc_path, 9)
expected_status.remove('A/B/E/alpha')
expected_status.add({
'A/C/epsilon' : Item(status=' ', wc_rev=9),
})
expected_status.tweak('A/B', 'A/mu', status=' ')
svntest.actions.run_and_verify_status(wc_path, expected_status)
class SVNLogParseError(Exception):
def __init__ (self, args=None):
self.args = args
def parse_log_output(log_lines):
"""Return a log chain derived from LOG_LINES.
A log chain is a list of hashes; each hash represents one log
message, in the order it appears in LOG_LINES (the first log
message in the data is also the first element of the list, and so
on).
Each hash contains the following keys/values:
'revision' ===> number
'author' ===> string
'date' ===> string
'msg' ===> string (the log message itself)
'lines' ===> number (so that it may be checked against rev)
If LOG_LINES contains changed-path information, then the hash
also contains
'paths' ===> list of strings
"""
header_re = re.compile ('^r([0-9]+) \| ' \
+ '([^|]*) \| ([^|]*) \| ([0-9]+) lines?')
chain = []
this_item = None
while 1:
try:
this_line = log_lines.pop (0)
except IndexError:
return chain
match = header_re.search (this_line)
if match and match.groups ():
this_item = {}
this_item['revision'] = match.group(1)
this_item['author'] = match.group(2)
this_item['date'] = match.group(3)
lines = string.atoi ((match.group (4)))
this_item['lines'] = lines
log_lines.pop (0)
msg = ''
for line in log_lines[0:lines]:
msg += line
del log_lines[0:lines]
elif this_line == msg_separator:
if this_item:
this_item['msg'] = msg
chain.append (this_item)
else: raise SVNLogParseError, "trailing garbage after log message"
return chain
class SVNUnexpectedLogs(svntest.Failure):
"Exception raised if a set of log messages doesn't meet expectations."
def __init__(self, msg, chain, field_selector = 'revision'):
"""Stores the log chain for later use. FIELD_SELECTOR indicates
which individual field to display when turning the exception into
text."""
svntest.Failure.__init__(self, msg)
self.chain = chain
self.field_selector = field_selector
def __str__(self):
msg = svntest.Failure.__str__(self)
if self.chain:
chain_data = list(self.chain)
for i in range(0, len(self.chain)):
chain_data[i] = self.chain[i][self.field_selector]
msg = msg + ': %s list was %s' % (self.field_selector, chain_data)
return msg
def check_log_chain (chain, revlist):
"""Verify that log chain CHAIN contains the right log messages for
revisions START to END (see documentation for parse_log_output() for
more about log chains).
Do nothing if the log chain's messages run from revision START to END
and each log message contains a line of the form
'Log message for revision N'
where N is the revision number of that commit. Verify that
author and date are present and look sane, but don't check them too
carefully.
Also verify that even numbered commit messages have three lines.
Raise an error if anything looks wrong.
"""
nbr_expected = len(revlist)
if len(chain) != nbr_expected:
raise SVNUnexpectedLogs('Number of elements in log chain and revision ' +
'list %s not equal' % revlist, chain)
missing_revs = []
for i in range(0, nbr_expected):
expect_rev = revlist[i]
log_item = chain[i]
saw_rev = string.atoi (log_item['revision'])
date = log_item['date']
author = log_item['author']
msg = log_item['msg']
if expect_rev != saw_rev:
missing_revs.append(expect_rev)
continue
date_re = re.compile ('[0-9]+')
if not date_re.search(date):
raise SVNUnexpectedLogs('Malformed date', chain, 'date')
author_re = re.compile ('[a-zA-Z]+')
if (not (author_re.search (author)
or author == ''
or author == '(no author)')):
raise SVNUnexpectedLogs('Malformed author', chain, 'author')
if (saw_rev % 2 == 0 and log_item['lines'] != 3):
raise SVNUnexpectedLogs('Malformed lines', chain, 'lines')
pattern = 'Log message for revision ' + `saw_rev`
msg_re = re.compile(pattern)
if not msg_re.search(msg):
raise SVNUnexpectedLogs("Malformed log message, expected '%s'" % msg,
chain)
nbr_missing_revs = len(missing_revs)
if nbr_missing_revs > 0:
raise SVNUnexpectedLogs('Unable to find expected revision(s) %s' %
missing_revs, chain)
def plain_log(sbox):
"'svn log', no args, top of wc"
guarantee_repos_and_wc(sbox)
was_cwd = os.getcwd()
os.chdir(sbox.wc_dir)
try:
output, err = svntest.actions.run_and_verify_svn(None, None, [], 'log')
log_chain = parse_log_output (output)
check_log_chain(log_chain, range(max_revision, 1 - 1, -1))
finally:
os.chdir (was_cwd)
def versioned_log_message(sbox):
"'svn commit -F foo' when foo is a versioned file"
sbox.build()
was_cwd = os.getcwd ()
os.chdir (sbox.wc_dir)
try:
iota_path = os.path.join ('iota')
mu_path = os.path.join ('A', 'mu')
log_path = os.path.join ('A', 'D', 'H', 'omega')
svntest.main.file_append (iota_path, "2")
svntest.actions.run_and_verify_svn("", None, SVNAnyOutput,
'ci', '-F', log_path)
svntest.actions.run_and_verify_svn ("", None, [],
'ci', '-F', log_path, '--force-log')
svntest.main.file_append (mu_path, "2")
svntest.actions.run_and_verify_svn("", None, SVNAnyOutput,
'ci', '-F', log_path, mu_path)
svntest.actions.run_and_verify_svn ("", None, [],
'ci',
'-F', log_path,
'--force-log', mu_path)
finally:
os.chdir (was_cwd)
def log_with_empty_repos(sbox):
"'svn log' on an empty repository"
svntest.main.safe_rmtree(sbox.repo_dir, 1)
svntest.main.create_repos(sbox.repo_dir)
svntest.main.set_repos_paths(sbox.repo_dir)
svntest.actions.run_and_verify_svn ("", None, [],
'log',
'--username', svntest.main.wc_author,
'--password', svntest.main.wc_passwd,
svntest.main.current_repo_url)
def log_where_nothing_changed(sbox):
"'svn log -rN some_dir_unchanged_in_N'"
sbox.build()
rho_path = os.path.join (sbox.wc_dir, 'A', 'D', 'G', 'rho')
svntest.main.file_append (rho_path, "some new material in rho")
svntest.actions.run_and_verify_svn(None, None, [], 'ci', '-m',
'log msg', rho_path)
H_path = os.path.join (sbox.wc_dir, 'A', 'D', 'H')
svntest.actions.run_and_verify_svn(None, None, [],
'log', '-r', '2', H_path)
def log_to_revision_zero(sbox):
"'svn log -v -r 1:0 wc_root'"
sbox.build()
svntest.actions.run_and_verify_svn(None, None, [],
'log', '-v',
'-r', '1:0', sbox.wc_dir)
def log_with_path_args(sbox):
"'svn log', with args, top of wc"
guarantee_repos_and_wc(sbox)
was_cwd = os.getcwd()
os.chdir(sbox.wc_dir)
try:
output, err = svntest.actions.run_and_verify_svn(
None, None, [],
'log', svntest.main.current_repo_url, 'A/D/G', 'A/D/H')
log_chain = parse_log_output (output)
check_log_chain(log_chain, [8, 6, 5, 3, 1])
finally:
os.chdir (was_cwd)
def dynamic_revision(sbox):
"'svn log -r COMMITTED' of dynamic/local WC rev"
guarantee_repos_and_wc(sbox)
was_cwd = os.getcwd()
os.chdir(sbox.wc_dir)
try:
for rev in ('HEAD', 'BASE', 'COMMITTED', 'PREV'):
svntest.actions.run_and_verify_svn(None, None, [], 'log', '-r', rev)
finally:
os.chdir(was_cwd)
def log_wc_with_peg_revision(sbox):
"'svn log wc_target@N'"
guarantee_repos_and_wc(sbox)
my_path = os.path.join(sbox.wc_dir, "A", "B", "E", "beta") + "@8"
output, err = svntest.actions.run_and_verify_svn(None, None, [],
'log', my_path)
check_log_chain(parse_log_output(output), [1])
def url_missing_in_head(sbox):
"'svn log target@N' when target removed from HEAD"
guarantee_repos_and_wc(sbox)
my_url = svntest.main.current_repo_url + "/A/B/E/alpha" + "@8"
output, err = svntest.actions.run_and_verify_svn(None, None, [],
'log', my_url)
check_log_chain(parse_log_output(output), [3, 1])
def log_through_copyfrom_history(sbox):
"'svn log TGT' with copyfrom history"
sbox.build()
wc_dir = sbox.wc_dir
msg_file=os.path.join (sbox.repo_dir, 'log-msg')
msg_file=os.path.abspath (msg_file)
mu_path = os.path.join (wc_dir, 'A', 'mu')
mu2_path = os.path.join (wc_dir, 'A', 'mu2')
mu_URL = svntest.main.current_repo_url + '/A/mu'
mu2_URL = svntest.main.current_repo_url + '/A/mu2'
msg2=""" Log message for revision 2
but with multiple lines
to test the code"""
msg4=""" Log message for revision 4
but with multiple lines
to test the code"""
msg6=""" Log message for revision 6
but with multiple lines
to test the code"""
log_file=open (msg_file, 'w')
log_file.write (msg2)
log_file.close ()
svntest.main.file_append (mu_path, "2")
svntest.actions.run_and_verify_svn (None, None, [], 'ci', wc_dir,
'-F', msg_file)
svntest.main.file_append (mu2_path, "this is mu2")
svntest.actions.run_and_verify_svn (None, None, [], 'add', mu2_path)
svntest.actions.run_and_verify_svn (None, None, [], 'ci', wc_dir,
'-m', "Log message for revision 3")
svntest.actions.run_and_verify_svn (None, None, [], 'rm', mu2_path)
log_file=open (msg_file, 'w')
log_file.write (msg4)
log_file.close ()
svntest.actions.run_and_verify_svn (None, None, [], 'ci', wc_dir,
'-F', msg_file)
svntest.main.file_append (mu_path, "5")
svntest.actions.run_and_verify_svn (None, None, [], 'ci', wc_dir,
'-m', "Log message for revision 5")
log_file=open (msg_file, 'w')
log_file.write (msg6)
log_file.close ()
svntest.actions.run_and_verify_svn (None, None, [],
'cp', '-r', '5', mu_URL, mu2_URL,
'-F', msg_file)
svntest.actions.run_and_verify_svn (None, None, [], 'up', wc_dir)
output, err = svntest.actions.run_and_verify_svn (None, None, [],
'log', mu2_path)
log_chain = parse_log_output (output)
check_log_chain(log_chain, [6, 5, 2, 1])
output, err = svntest.actions.run_and_verify_svn (None, None, [],
'log', mu2_URL)
log_chain = parse_log_output (output)
check_log_chain(log_chain, [6, 5, 2, 1])
peg_mu2_path = mu2_path + "@3"
output, err = svntest.actions.run_and_verify_svn (None, None, [],
'log', '-r', '3',
peg_mu2_path)
log_chain = parse_log_output (output)
check_log_chain(log_chain, [3])
peg_mu2_URL = mu2_URL + "@3"
output, err = svntest.actions.run_and_verify_svn (None, None, [],
'log', '-r', '3',
peg_mu2_URL)
log_chain = parse_log_output (output)
check_log_chain(log_chain, [3])
output, err = svntest.actions.run_and_verify_svn (None, None, [],
'log', '-r', '2',
mu2_path)
log_chain = parse_log_output (output)
check_log_chain (log_chain, [2])
output, err = svntest.actions.run_and_verify_svn (None, None, [],
'log', '-r', '2',
mu2_URL)
log_chain = parse_log_output (output)
check_log_chain(log_chain, [2])
def escape_control_chars(sbox):
"mod_dav_svn must escape invalid XML control chars"
dump_str = """SVN-fs-dump-format-version: 2
UUID: ffcae364-69ee-0310-a980-ca5f10462af2
Revision-number: 0
Prop-content-length: 56
Content-length: 56
K 8
svn:date
V 27
2005-01-24T10:09:21.759592Z
PROPS-END
Revision-number: 1
Prop-content-length: 128
Content-length: 128
K 7
svn:log
V 100
This msg contains a Ctrl-T (\x14) and a Ctrl-I (\t).
The former might be escaped, but the latter never.
K 10
svn:author
V 7
jrandom
K 8
svn:date
V 27
2005-01-24T10:09:22.012524Z
PROPS-END
"""
svntest.main.safe_rmtree(sbox.repo_dir, 1)
svntest.main.create_repos(sbox.repo_dir)
svntest.main.set_repos_paths(sbox.repo_dir)
URL = svntest.main.current_repo_url
output, errput = \
svntest.main.run_command_stdin(
"%s load --quiet %s" % (svntest.main.svnadmin_binary, sbox.repo_dir),
None, 1, dump_str)
output, errput = svntest.actions.run_and_verify_svn ("", None, [], 'log', URL)
match_unescaped_ctrl_re = "This msg contains a Ctrl-T \(.\) " \
"and a Ctrl-I \(\t\)\."
match_escaped_ctrl_re = "^This msg contains a Ctrl-T \(\?\\\\020\) " \
"and a Ctrl-I \(\t\)\."
matched = None
for line in output:
if re.match (match_unescaped_ctrl_re, line) \
or re.match (match_escaped_ctrl_re, line):
matched = 1
if not matched:
raise svntest.Failure ("log message not transmitted properly:" +
str(output) + "\n" + "error: " + str(errput))
def log_xml_empty_date(sbox):
"svn log --xml must not print empty date elements"
sbox.build()
svntest.actions.enable_revprop_changes(svntest.main.current_repo_dir)
date_re = re.compile('<date');
output, errput = svntest.actions.run_and_verify_svn("", None, [],
'log', '--xml', '-r1',
sbox.wc_dir)
matched = 0
for line in output:
if date_re.search(line):
matched = 1
if not matched:
raise svntest.Failure ("log contains no date element")
svntest.actions.run_and_verify_svn("", None, [],
'pdel', '--revprop', '-r1', 'svn:date',
sbox.wc_dir)
output, errput = svntest.actions.run_and_verify_svn("", None, [],
'log', '--xml', '-r1',
sbox.wc_dir)
for line in output:
if date_re.search(line):
raise svntest.Failure ("log contains date element when svn:date is empty")
def log_limit(sbox):
"svn log --limit"
guarantee_repos_and_wc(sbox)
out, err = svntest.actions.run_and_verify_svn(None, None, [],
'log', '--limit', '2',
svntest.main.current_repo_url)
log_chain = parse_log_output (out)
check_log_chain(log_chain, [9, 8])
out, err = svntest.actions.run_and_verify_svn(None, None, [],
'log', '--limit', '2',
svntest.main.current_repo_url,
'A/B')
log_chain = parse_log_output (out)
check_log_chain(log_chain, [9, 6])
out, err = svntest.actions.run_and_verify_svn(None, None, [],
'log', '--limit', '2',
'--revision', '2:HEAD',
svntest.main.current_repo_url,
'A/B')
log_chain = parse_log_output (out)
check_log_chain(log_chain, [3, 6])
out, err = svntest.actions.run_and_verify_svn(None, None, [],
'log', '--limit', '2',
'--revision', '1',
svntest.main.current_repo_url,
'A/B')
log_chain = parse_log_output (out)
check_log_chain(log_chain, [1])
must_be_positive = ".*Argument to --limit must be positive.*"
svntest.actions.run_and_verify_svn(None, None, must_be_positive,
'log', '--limit', '0', '--revision', '1',
svntest.main.current_repo_url, 'A/B')
svntest.actions.run_and_verify_svn(None, None, must_be_positive,
'log', '--limit', '-1', '--revision', '1',
svntest.main.current_repo_url, 'A/B')
def log_base_peg(sbox):
"run log on an @BASE target"
guarantee_repos_and_wc(sbox)
target = os.path.join(sbox.wc_dir, 'A', 'B', 'E', 'beta') + '@BASE'
out, err = svntest.actions.run_and_verify_svn(None, None, [], 'log', target)
log_chain = parse_log_output(out)
check_log_chain(log_chain, [9, 1])
svntest.actions.run_and_verify_svn(None, None, [], 'update', '-r', '1',
sbox.wc_dir)
out, err = svntest.actions.run_and_verify_svn(None, None, [], 'log', target)
log_chain = parse_log_output(out)
check_log_chain(log_chain, [1])
test_list = [ None,
plain_log,
versioned_log_message,
log_with_empty_repos,
log_where_nothing_changed,
log_to_revision_zero,
dynamic_revision,
log_with_path_args,
log_wc_with_peg_revision,
url_missing_in_head,
log_through_copyfrom_history,
escape_control_chars,
log_xml_empty_date,
log_limit,
log_base_peg,
]
if __name__ == '__main__':
svntest.main.run_tests(test_list)