#!/usr/local/bin/python3.12
#
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#

# log-police.py: Ensure that log messages end with a single newline.
# See usage() function for details, or just run with no arguments.

import os
import sys
import getopt
try:
  my_getopt = getopt.gnu_getopt
except AttributeError:
  my_getopt = getopt.getopt

import svn
import svn.fs
import svn.repos
import svn.core


def fix_log_message(log_message):
  """Return a fixed version of LOG_MESSAGE.  By default, this just
  means ensuring that the result ends with exactly one newline and no
  other whitespace.  But if you want to do other kinds of fixups, this
  function is the place to implement them -- all log message fixing in
  this script happens here."""
  return log_message.rstrip() + "\n"


def fix_txn(fs, txn_name):
  "Fix up the log message for txn TXN_NAME in FS.  See fix_log_message()."
  txn = svn.fs.svn_fs_open_txn(fs, txn_name)
  log_message = svn.fs.svn_fs_txn_prop(txn, "svn:log")
  if log_message is not None:
    new_message = fix_log_message(log_message)
    if new_message != log_message:
      svn.fs.svn_fs_change_txn_prop(txn, "svn:log", new_message)


def fix_rev(fs, revnum):
  "Fix up the log message for revision REVNUM in FS.  See fix_log_message()."
  log_message = svn.fs.svn_fs_revision_prop(fs, revnum, 'svn:log')
  if log_message is not None:
    new_message = fix_log_message(log_message)
    if new_message != log_message:
      svn.fs.svn_fs_change_rev_prop(fs, revnum, "svn:log", new_message)


def usage_and_exit(error_msg=None):
  """Write usage information and exit.  If ERROR_MSG is provide, that
  error message is printed first (to stderr), the usage info goes to
  stderr, and the script exits with a non-zero status.  Otherwise,
  usage info goes to stdout and the script exits with a zero status."""
  import os.path
  stream = error_msg and sys.stderr or sys.stdout
  if error_msg:
    stream.write("ERROR: %s\n\n" % error_msg)
  stream.write("USAGE: %s [-t TXN_NAME | -r REV_NUM | --all-revs] REPOS\n"
               % (os.path.basename(sys.argv[0])))
  stream.write("""
Ensure that log messages end with exactly one newline and no other
whitespace characters.  Use as a pre-commit hook by passing '-t TXN_NAME';
fix up a single revision by passing '-r REV_NUM'; fix up all revisions by
passing '--all-revs'.  (When used as a pre-commit hook, may modify the
svn:log property on the txn.)
""")
  sys.exit(error_msg and 1 or 0)


def main(ignored_pool, argv):
  repos_path = None
  txn_name = None
  rev_name = None
  all_revs = False

  try:
    opts, args = my_getopt(argv[1:], 't:r:h?', ["help", "all-revs"])
  except:
    usage_and_exit("problem processing arguments / options.")
  for opt, value in opts:
    if opt == '--help' or opt == '-h' or opt == '-?':
      usage_and_exit()
    elif opt == '-t':
      txn_name = value
    elif opt == '-r':
      rev_name = value
    elif opt == '--all-revs':
      all_revs = True
    else:
      usage_and_exit("unknown option '%s'." % opt)

  if txn_name is not None and rev_name is not None:
    usage_and_exit("cannot pass both -t and -r.")
  if txn_name is not None and all_revs:
    usage_and_exit("cannot pass --all-revs with -t.")
  if rev_name is not None and all_revs:
    usage_and_exit("cannot pass --all-revs with -r.")
  if rev_name is None and txn_name is None and not all_revs:
    usage_and_exit("must provide exactly one of -r, -t, or --all-revs.")
  if len(args) != 1:
    usage_and_exit("only one argument allowed (the repository).")

  repos_path = svn.core.svn_path_canonicalize(args[0])

  # A non-bindings version of this could be implemented by calling out
  # to 'svnlook getlog' and 'svnadmin setlog'.  However, using the
  # bindings results in much simpler code.

  fs = svn.repos.svn_repos_fs(svn.repos.svn_repos_open(repos_path))
  if txn_name is not None:
    fix_txn(fs, txn_name)
  elif rev_name is not None:
    fix_rev(fs, int(rev_name))
  elif all_revs:
    # Do it such that if we're running on a live repository, we'll
    # catch up even with commits that came in after we started.
    last_youngest = 0
    while True:
      youngest = svn.fs.svn_fs_youngest_rev(fs)
      if youngest >= last_youngest:
        for this_rev in range(last_youngest, youngest + 1):
          fix_rev(fs, this_rev)
        last_youngest = youngest + 1
      else:
        break


if __name__ == '__main__':
  sys.exit(svn.core.run_app(main, sys.argv))
