#!/usr/bin/env python3
#
# $NetBSD$
#
# Copyright (c) 2024 The NetBSD Foundation, Inc.
# All rights reserved.
#
# This code is derived from software contributed to The NetBSD Foundation
# by Thomas Klausner.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

import argparse
import os
from pathlib import Path
import subprocess


def file_path(path):
    '''Check if argument is a valid file path.'''
    if Path(path).is_file():
        return str(Path(path).resolve())
    raise argparse.ArgumentTypeError(f"{path} is not a valid file path")


def dir_path(path):
    '''Check if argument is a valid directory path.'''
    if Path(path).is_dir():
        return str(Path(path).resolve())
    raise argparse.ArgumentTypeError(f"{path} is not a valid directory path")


parser = argparse.ArgumentParser(description='Convert CVS repository to git')
parser.add_argument('-m', dest='mailmap', type=file_path,
                    help='use %(dest)s to map UNIX logins to names ' +
                    'and email addresses')
parser.add_argument('-f', dest='fixup_sql_script', type=file_path,
                    help='run %(dest)s on fossil database to fix problems')
parser.add_argument('-s', dest='merge_limit', type=int,
                    help='pass %(dest)s to cvs2fossil ' +
                    '(merge window time in seconds)')
parser.add_argument('source', type=dir_path,
                    help='source CVS repository master')
# may not exist yet, can't use dir_path
parser.add_argument('destination', type=str,
                    help='base name of target git repository')
args = parser.parse_args()

run_args = ['cvs2fossil', '-m']
if args.fixup_sql_script:
    run_args += ['-f', args.fixup_sql_script]
if args.merge_limit:
    run_args += ['-s', str(args.merge_limit)]
run_args += [args.source, args.destination]

# convert to fossil using cvs2fossil
subprocess.run(run_args, check=True)
# cvs2fossil creates {args.destination} and {args.destination}.fossil,
# but we don't need the former
os.remove(args.destination)
# export from fossil
with open(f'{args.destination}.fossil.export', 'wb') as output:
    subprocess.run(['fossil1', 'export', '-R', f'{args.destination}.fossil'],
                   stdout=output, check=True)
# import to git
subprocess.run(['git', 'init', f'{args.destination}.git'], check=True)
with open(f'{args.destination}.fossil.export', 'rb') as input:
    subprocess.run(['git', 'fast-import'], check=True,
                   cwd=f'{args.destination}.git', stdin=input)
# HEAD is broken, fix it
with open(f'{args.destination}.git/.git/HEAD', 'w', encoding='ASCII') as conf:
    conf.write('ref: refs/heads/trunk\n')
# rename 'trunk' to 'main'
subprocess.run(['git', 'branch', 'main', 'trunk'], check=True,
               cwd=f'{args.destination}.git')
subprocess.run(['git', 'switch', 'main'], check=True,
               cwd=f'{args.destination}.git')
subprocess.run(['git', 'branch', '-d', 'trunk'], check=True,
               cwd=f'{args.destination}.git')

# fix author names
if args.mailmap:
    run_args = ['git', 'clone', f'{args.destination}.git',
                f'{args.destination}.rewrite.git']
    subprocess.run(run_args, check=True)
    run_args = ['git', 'filter-repo', '--force']
    # and apply author fixes
    if args.mailmap:
        run_args.append('--mailmap')
        run_args.append(f'{args.mailmap}')
    subprocess.run(run_args, check=True,
                   cwd=f'{args.destination}.rewrite.git')
    os.rename(f'{args.destination}.git', f'{args.destination}.git.old')
    os.rename(f'{args.destination}.rewrite.git', f'{args.destination}.git')
