/*
 *	aegis - project change supervisor
 *	Copyright (C) 1991-1999, 2001-2003 Peter Miller;
 *	All rights reserved.
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 * MANIFEST: functions to implement new release
 */

#include <ac/ctype.h>
#include <ac/stdio.h>
#include <ac/string.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <ael/project/projects.h>
#include <aenrls.h>
#include <arglex2.h>
#include <change/branch.h>
#include <change/file.h>
#include <commit.h>
#include <dir.h>
#include <error.h>
#include <file.h>
#include <gonzo.h>
#include <help.h>
#include <lock.h>
#include <log.h>
#include <mem.h>
#include <os.h>
#include <progname.h>
#include <project.h>
#include <project/file.h>
#include <project/history.h>
#include <sub.h>
#include <trace.h>
#include <undo.h>
#include <user.h>

#define GIVEN -1
#define NOT_GIVEN -2


static void
new_release_usage(void)
{
    const char      *progname;

    progname = progname_get();
    fprintf
    (
	stderr,
	"usage: %s -New_ReLeaSe <name> [ <option>... ]\n",
	progname
    );
    fprintf(stderr, "       %s -New_ReLeaSe -List [ <option>... ]\n", progname);
    fprintf(stderr, "       %s -New_ReLeaSe -Help\n", progname);
    quit(1);
}


static void
new_release_help(void)
{
    help("aenrls", new_release_usage);
}


static void
new_release_list(void)
{
    arglex();
    while (arglex_token != arglex_token_eoln)
	generic_argument(new_release_usage);
    list_projects(0, 0, 0);
}


typedef struct copy_tree_arg_ty copy_tree_arg_ty;
struct copy_tree_arg_ty
{
    string_ty	    *from;
    string_ty	    *to;
};


static void
copy_tree_callback(void *arg, dir_walk_message_ty message, string_ty *path,
    struct stat *st)
{
    string_ty	    *s1;
    string_ty	    *s2;
    copy_tree_arg_ty *info;

    trace(("copy_tree_callback(arg = %08lX, message = %d, path = %08lX, "
	"st = %08lX)\n{\n", (long)arg, message, (long)path, (long)st));
    info = (copy_tree_arg_ty *)arg;
    trace_string(path->str_text);
    s1 = os_below_dir(info->from, path);
    assert(s1);
    trace_string(s1->str_text);
    if (!s1->str_length)
	s2 = str_copy(info->to);
    else
	s2 = str_format("%S/%S", info->to, s1);
    trace_string(s2->str_text);
    switch (message)
    {
    case dir_walk_dir_before:
	if (s1->str_length)
	{
	    os_mkdir(s2, st->st_mode & 07755);
	    undo_rmdir_errok(s2);
	}
	break;

    case dir_walk_file:
	/*
	 * copy the file
	 */
	copy_whole_file(path, s2, 1);
	undo_unlink_errok(s2);
	os_chmod(s2, st->st_mode & 07755);
	break;

    case dir_walk_dir_after:
    case dir_walk_special:
    case dir_walk_symlink:
	break;
    }
    str_free(s2);
    str_free(s1);
    trace(("}\n"));
}


static void
new_release_main(void)
{
    sub_context_ty  *scp;
    string_ty	    *ip;
    string_ty	    *bl;
    string_ty	    *hp;
    size_t	    j;
    size_t	    k;
    pstate_ty	    *pstate_data[2];
    string_ty	    *home;
    string_ty	    *s1;
    string_ty	    *s2;
    string_ty	    *project_name[2];
    int		    project_name_count;
    project_ty	    *pp[2];
    project_ty	    *ppp;
    change_ty	    *cp;
    cstate_history_ty *chp;
    cstate_ty       *cstate_data;
    copy_tree_arg_ty info;
    log_style_ty    log_style;
    user_ty	    *up;
    user_ty	    *pup;
    user_ty	    *pup1;
    long	    new_version_number[10];
    int		    new_version_number_length;
    string_ty	    *new_version_string;
    project_ty	    *version_pp[SIZEOF(new_version_number)];
    long	    old_version_number[SIZEOF(new_version_number)];
    int		    old_version_number_length;
    long	    change_number;

    trace(("new_release_main()\n{\n"));
    arglex();
    log_style = log_style_create_default;
    home = 0;
    project_name_count = 0;
    new_version_number[0] = NOT_GIVEN;
    new_version_number[1] = NOT_GIVEN;
    new_version_number_length = 0;
    new_version_string = 0;
    while (arglex_token != arglex_token_eoln)
    {
	switch (arglex_token)
	{
	default:
	    generic_argument(new_release_usage);
	    continue;

	case arglex_token_project:
	    if (arglex() != arglex_token_string)
		option_needs_name(arglex_token_project, new_release_usage);
	    /* fall through... */

	case arglex_token_string:
	    if (project_name_count >= 2)
		fatal_intl(0, i18n("too many proj name"));
	    project_name[project_name_count++] =
		str_from_c(arglex_value.alv_string);
	    break;

	case arglex_token_directory:
	    if (arglex() != arglex_token_string)
		option_needs_dir(arglex_token_directory, new_release_usage);
	    if (home)
	    {
		duplicate_option_by_name
		(
		    arglex_token_directory,
		    new_release_usage
		);
	    }
	    s1 = str_from_c(arglex_value.alv_string);
	    os_become_orig();
	    home = os_pathname(s1, 1);
	    os_become_undo();
	    str_free(s1);
	    break;

	case arglex_token_major:
	    if (new_version_number[0] != NOT_GIVEN)
		duplicate_option(new_release_usage);
	    scp = sub_context_new();
	    sub_var_set_charstar
	    (
		scp,
		"Name1",
		arglex_token_name(arglex_token_major)
	    );
	    sub_var_set_charstar
	    (
		scp,
		"Name2",
		arglex_token_name(arglex_token_version)
	    );
	    error_intl(scp, "warning: $name1 obsolete, use $name2 option");
	    sub_context_delete(scp);
	    if (new_version_number_length < 1)
		new_version_number_length = 1;
	    if (arglex() != arglex_token_number)
	    {
		new_version_number[0] = GIVEN;
		continue;
	    }
	    if (arglex_value.alv_number < 0)
		option_needs_number(arglex_token_major, new_release_usage);
	    new_version_number[0] = arglex_value.alv_number;
	    break;

	case arglex_token_minor:
	    if (new_version_number[1] != NOT_GIVEN)
		duplicate_option(new_release_usage);
	    scp = sub_context_new();
	    sub_var_set_charstar
	    (
		scp,
		"Name1",
		arglex_token_name(arglex_token_minor)
	    );
	    sub_var_set_charstar
	    (
		scp,
		"Name2",
		arglex_token_name(arglex_token_version)
	    );
	    error_intl
	    (
		scp,
		i18n("warning: $name1 obsolete, use $name2 option")
	    );
	    sub_context_delete(scp);
	    while (new_version_number_length < 2)
		new_version_number[new_version_number_length++] = NOT_GIVEN;
	    if (arglex() != arglex_token_number)
	    {
		new_version_number[1] = GIVEN;
		continue;
	    }
	    if (arglex_value.alv_number <= 0)
		option_needs_number(arglex_token_minor, new_release_usage);
	    new_version_number[1] = arglex_value.alv_number;
	    break;

	case arglex_token_version:
	    if (new_version_string)
		duplicate_option(new_release_usage);
	    switch (arglex())
	    {
	    default:
		option_needs_string(arglex_token_version, new_release_usage);

	    case arglex_token_number:
	    case arglex_token_string:
		break;
	    }
	    new_version_string = str_from_c(arglex_value.alv_string);
	    break;

	case arglex_token_nolog:
	    if (log_style == log_style_none)
		duplicate_option(new_release_usage);
	    log_style = log_style_none;
	    break;

	case arglex_token_wait:
	case arglex_token_wait_not:
	    user_lock_wait_argument(new_release_usage);
	    break;
	}
	arglex();
    }
    if (!project_name_count)
	fatal_intl(0, i18n("no project name"));
    if (new_version_number_length > 0)
    {
	if (new_version_string)
	{
	    error_intl(0, i18n("don't mix old and new version options"));
	    new_release_usage();
	}
	while (new_version_number_length < 2)
	    new_version_number[new_version_number_length++] = 0;
    }

    /*
     * locate OLD project data
     */
    pp[0] = project_alloc(project_name[0]);
    project_bind_existing(pp[0]);
    if (pp[0]->parent)
	goto too_modern;
    pstate_data[0] = project_pstate_get(pp[0]);

    /*
     * You may only use this command for old-style (pre branching)
     * projects.  Only old-style projects (pre-3.0) define the
     * version_major and version_minor fields in the project state
     * data.
     */
    if (!pstate_data[0]->version_major && !pstate_data[0]->version_minor)
    {
	too_modern:
	project_fatal(pp[0], 0, i18n("bad nrls, too modern"));
    }

    /*
     * locate user data
     */
    up = user_executing(pp[0]);

    /*
     * it is an error if the current user is not an administrator
     * of the old project.
     */
    if (!project_administrator_query(pp[0], user_name(up)))
	project_fatal(pp[0], 0, i18n("not an administrator"));

    /*
     * figure out the old version number
     *
     * Because we know that the old project is in the pre-3.0
     * format, at this point we know definitely that the version
     * fields are set.
     */
    old_version_number_length = 2;
    old_version_number[0] = pstate_data[0]->version_major;
    old_version_number[1] = pstate_data[0]->version_minor;

    if (project_name_count < 2)
    {
	long		junk[10];
	int		junk_length;

	/*
	 * Default the new project name from the old, when the
	 * new name was not given.
	 */
	project_name[1] = str_copy(project_name[0]);
	project_name_count = 2;

	/*
	 * Throw away any version information which may be
	 * contained in the project name.  It is usually
	 * redundant, when it is present at all.
	 */
	extract_version_from_project_name
	(
	    &project_name[1],
	    junk,
	    (int)SIZEOF(junk),
	    &junk_length
	);
    }
    else
    {
	/*
	 * Extract the version number information from the new
	 * project name.
	 */
	extract_version_from_project_name
	(
	    &project_name[1],
	    new_version_number,
	    (int)SIZEOF(new_version_number),
	    &new_version_number_length
	);
    }

    /*
     * Make sure the project name is acceptable.
     */
    if (!project_name_ok(project_name[1]))
	fatal_bad_project_name(project_name[1]);

    /*
     * Default to a minor release incriment.
     *
     * Only do this if there is no version number implicit in the
     * project name AND no version string was given.
     *
     * This test is done BEFORE the version string break up, because
     * if the version string is the empty string, it means to use NO
     * version numbers.
     */
    if (!new_version_number_length && !new_version_string)
    {
	new_version_number_length = 2;
	new_version_number[0] = NOT_GIVEN;
	new_version_number[1] = GIVEN;
    }

    /*
     * If the user specified a version string on the command line,
     * break it up into its component parts.  If there was version
     * numbers in the NEW project name, the version numbers
     * determined here will be appended to them.
     */
    if (new_version_string && new_version_string->str_length)
    {
	int		err;

	err =
	    break_up_version_string
	    (
		new_version_string->str_text,
		new_version_number,
		(int)SIZEOF(new_version_number),
		&new_version_number_length,
		0
	    );
	if (err)
	{
	    scp = sub_context_new();
	    sub_var_set_string(scp, "Number", new_version_string);
	    fatal_intl(scp, i18n("bad version $number"));
	    /* NOTREACHED */
	    sub_context_delete(scp);
	}
    }

    /*
     * Figure the new version number.  This code only ever operates
     * for the -major and -minor options, when they are not given
     * explicit numbers.
     */
    for (j = 0; j < new_version_number_length; ++j)
    {
	switch (new_version_number[j])
	{
	case NOT_GIVEN:
	    /*
	     * Use the old version number if available.
	     * Otherwise, the major version defaults to 1,
	     * and all others default to 0.
	     */
	    if (j < old_version_number_length)
		new_version_number[j] = old_version_number[j];
	    else
		new_version_number[j] = !j;
	    break;

	case GIVEN:
	    /*
	     * Use the old version number if available.
	     * Otherwise, the major version defaults to 1,
	     * and all others default to 0.
	     */
	    if (j < old_version_number_length)
		new_version_number[j] = old_version_number[j];
	    else
		new_version_number[j] = !j;

	    /*
	     * Incriment the version number by one.
	     */
	    new_version_number[j]++;

	    /*
	     * Set all of the remaining components to zero.
	     * (Usually these are the minor components,
	     * after using the -major option.)
	     */
	    for (k = j + 1; k < new_version_number_length; ++k)
	    {
		if (new_version_number[k] == NOT_GIVEN)
		    new_version_number[k] = 0;
	    }
	    break;

	default:
	    break;
	}
    }

    /*
     * locate NEW project data
     *
     * It is a fatal error if the project name already exists.
     */
    pp[1] = project_alloc(project_name[1]);
    project_bind_new(pp[1]);
    pp[1]->uid = pp[0]->uid;
    pp[1]->gid = pp[0]->gid;
    pup = project_user(pp[0]);

    /*
     * if no project directory was specified
     * create the directory in their home directory.
     */
    if (!home)
    {
	int		max;

	s2 = user_default_project_directory(pup);
	assert(s2);
	os_become_orig();
	max = os_pathconf_name_max(s2);
	os_become_undo();
	if (project_name[1]->str_length > max)
	    fatal_project_name_too_long(project_name[1], max);
	home = str_format("%S/%S", s2, project_name[1]);
	str_free(s2);

	scp = sub_context_new();
	sub_var_set_string(scp, "File_Name", home);
	project_verbose(pp[1], scp, i18n("proj dir $filename"));
	sub_context_delete(scp);
    }
    project_home_path_set(pp[1], home);
    str_free(home);

    /*
     * take the relevant locks
     */
    project_pstate_lock_prepare(pp[1]);
    project_baseline_read_lock_prepare(pp[0]);
    gonzo_gstate_lock_prepare_new();
    lock_take();
    pstate_data[0] = project_pstate_get(pp[0]);
    pstate_data[1] = project_pstate_get(pp[1]);

    /*
     * Create the directory and subdirectories.
     * It is an error if the directories can't be created.
     */
    home = project_home_path_get(pp[1]);
    bl = project_baseline_path_get(pp[1], 0);
    hp = project_history_path_get(pp[1]);
    ip = project_info_path_get(pp[1]);
    project_become(pp[1]);
    os_mkdir(home, 02755);
    undo_rmdir_errok(home);
    os_mkdir(bl, 02755);
    undo_rmdir_errok(bl);
    os_mkdir(hp, 02755);
    undo_rmdir_errok(hp);
    os_mkdir(ip, 02755);
    undo_rmdir_errok(ip);
    project_become_undo();

    /*
     * create a new release state file
     */
    pstate_data[1]->next_test_number = pstate_data[0]->next_test_number;
    project_version_previous_set(pp[1], project_version_get(pp[0]));

    /* administrators */
    for (j = 0;; ++j)
    {
	s1 = project_administrator_nth(pp[0], j);
	if (!s1)
	    break;
	project_administrator_add(pp[1], s1);
    }

    /* developers */
    for (j = 0;; ++j)
    {
	s1 = project_developer_nth(pp[0], j);
	if (!s1)
	    break;
	project_developer_add(pp[1], s1);
    }

    /* reviewers */
    for (j = 0;; ++j)
    {
	s1 = project_reviewer_nth(pp[0], j);
	if (!s1)
	    break;
	project_reviewer_add(pp[1], s1);
    }

    /* integrators */
    for (j = 0;; ++j)
    {
	s1 = project_integrator_nth(pp[0], j);
	if (!s1)
	    break;
	project_integrator_add(pp[1], s1);
    }

    /*
     * Copy the project attributes across.
     *
     * Please keep this in the same order as aegis/pattr.def
     * to make sure none are missed.
     *
     * DO NOT copy the major and minor version numbers across,
     * because we are creating a new-style project.
     */
    project_description_set(pp[1], project_description_get(pp[0]));
    project_developer_may_review_set
    (
	pp[1],
	project_developer_may_review_get(pp[0])
    );
    project_developer_may_integrate_set
    (
	pp[1],
	project_developer_may_integrate_get(pp[0])
    );
    project_reviewer_may_integrate_set
    (
	pp[1],
	project_reviewer_may_integrate_get(pp[0])
    );
    project_developers_may_create_changes_set
    (
	pp[1],
	project_developers_may_create_changes_get(pp[0])
    );
    project_forced_develop_begin_notify_command_set
    (
	pp[1],
	project_forced_develop_begin_notify_command_get(pp[0])
    );
    project_develop_end_notify_command_set
    (
	pp[1],
	project_develop_end_notify_command_get(pp[0])
    );
    project_develop_end_undo_notify_command_set
    (
	pp[1],
	project_develop_end_undo_notify_command_get(pp[0])
    );
    project_review_pass_notify_command_set
    (
	pp[1],
	project_review_pass_notify_command_get(pp[0])
    );
    project_review_pass_undo_notify_command_set
    (
	pp[1],
	project_review_pass_undo_notify_command_get(pp[0])
    );
    project_review_fail_notify_command_set
    (
	pp[1],
	project_review_fail_notify_command_get(pp[0])
    );
    project_integrate_pass_notify_command_set
    (
	pp[1],
	project_integrate_pass_notify_command_get(pp[0])
    );
    project_integrate_fail_notify_command_set
    (
	pp[1],
	project_integrate_fail_notify_command_get(pp[0])
    );
    project_default_development_directory_set
    (
	pp[1],
	project_default_development_directory_get(pp[0])
    );
    project_umask_set(pp[1], project_umask_get(pp[0]));
    project_default_test_exemption_set
    (
	pp[1],
	project_default_test_exemption_get(pp[0])
    );
    project_minimum_change_number_set
    (
	pp[1],
	project_minimum_change_number_get(pp[0])
    );
    project_reuse_change_numbers_set
    (
	pp[1],
	project_reuse_change_numbers_get(pp[0])
    );
    project_minimum_branch_number_set
    (
	pp[1],
	project_minimum_branch_number_get(pp[0])
    );

    /*
     * add a row to the project table
     */
    gonzo_project_add(pp[1]);

    /*
     * create each of the branches
     * (attributes are inherited)
     */
    ppp = pp[1];
    for (j = 0; j < new_version_number_length; ++j)
    {
	trace(("ppp = %8.8lX\n", (long)ppp));
	change_number = magic_zero_encode(new_version_number[j]);
	trace(("change_number = %ld;\n", change_number));
	ppp = project_new_branch(ppp, up, change_number, (string_ty *)0);
	version_pp[j] = ppp;
    }

    /*
     * Add all of the files to the final branch.
     */
    change_number = project_next_change_number(ppp, 1);
    cp = change_alloc(ppp, change_number);
    change_bind_new(cp);
    cstate_data = change_cstate_get(cp);
    scp = sub_context_new();
    sub_var_set_string(scp, "Name", project_name[0]);
    cstate_data->brief_description =
	subst_intl(scp, i18n("New release derived from $name."));
    sub_context_delete(scp);
    cstate_data->cause = change_cause_internal_enhancement;
    cstate_data->test_exempt = 1;
    cstate_data->test_baseline_exempt = 1;
    project_change_append(ppp, change_number, 0);

    /*
     * lots of fake history so we don't confuse
     * anything later with otherwise illegal state transitions
     */
    chp = change_history_new(cp, up);
    chp->what = cstate_history_what_new_change;
    chp = change_history_new(cp, up);
    chp->what = cstate_history_what_develop_begin;
    chp = change_history_new(cp, up);
    cstate_data->development_directory = os_edit_filename(0);
    chp->what = cstate_history_what_develop_end;
    chp = change_history_new(cp, up);
    chp->what = cstate_history_what_review_pass;
    chp = change_history_new(cp, up);
    chp->what = cstate_history_what_integrate_begin;
    cstate_data->state = cstate_state_being_integrated;
    bl = project_baseline_path_get(ppp, 0);
    cstate_data->integration_directory = str_copy(bl);

    /*
     * update the copyright years of the change
     */
    change_copyright_years_now(cp);

    /*
     * update the copyright years of the branch
     * (a) from the original project
     * (b) from the fake change that created the files
     */
    project_copyright_years_merge(ppp, project_change_get(pp[0]));
    project_copyright_years_merge(ppp, cp);

    /*
     * add all of the files to the change
     */
    for (j = 0;; ++j)
    {
	fstate_src_ty   *p_src_data;
	fstate_src_ty   *p1_src_data;
	fstate_src_ty   *c_src_data;

	p_src_data = project_file_nth(pp[0], j, view_path_extreme);
	if (!p_src_data)
	    break;

	p1_src_data = project_file_new(ppp, p_src_data->file_name);
	p1_src_data->action = file_action_create;
	p1_src_data->usage = p_src_data->usage;

	c_src_data = change_file_new(cp, p_src_data->file_name);
	c_src_data->action = file_action_create;
	c_src_data->usage = p_src_data->usage;

	/*
	 * copy testing correlations
	 */
	if (p_src_data->test && p_src_data->test->length)
	{
	    size_t	    m;

	    p1_src_data->test = fstate_src_test_list_type.alloc();
	    for (m = 0; m < p_src_data->test->length; ++m)
	    {
		string_ty	**addr_p;
		type_ty		*type_p;

		addr_p =
		    fstate_src_test_list_type.list_parse
		    (
			p1_src_data->test,
			&type_p
		    );
		assert(type_p == &string_type);
		*addr_p = str_copy(p_src_data->test->list[m]);
	    }
	}
    }

    /*
     * Open the log file
     */
    pup1 = project_user(pp[1]);
    s1 = str_format("%S/%s.log", bl, progname_get());
    log_open(s1, pup1, log_style);
    str_free(s1);

    /*
     * copy files from old baseline to new baseline
     */
    info.from = project_baseline_path_get(pp[0], 1);
    info.to = bl;
    project_verbose(ppp, 0, i18n("copy baseline"));
    project_become(ppp);
    dir_walk(info.from, copy_tree_callback, &info);
    project_become_undo();

    /*
     * Build all of the difference files,
     * and record the fingerprints.
     */
    for (j = 0;; ++j)
    {
	fstate_src_ty   *src_data;
	string_ty	*original;
	string_ty	*path;
	string_ty	*path_d;

	/*
	 * find the relevant change src data
	 */
	src_data = project_file_nth(ppp, j, view_path_extreme);
	if (!src_data)
	    break;

	/*
	 * generated files are not fingerprinted or differenced
	 */
	switch (src_data->usage)
	{
	case file_usage_build:
	    continue;

	case file_usage_source:
	case file_usage_config:
	case file_usage_test:
	case file_usage_manual_test:
	    break;
	}

	/*
	 * build the path to the source file
	 */
	path = str_format("%S/%S", bl, src_data->file_name);
	assert(path);
	trace_string(path->str_text);

	/*
	 * Record the source file's fingerprint.
	 */
	if (!src_data->file_fp)
	    src_data->file_fp = fingerprint_type.alloc();
	project_become(ppp);
	change_fingerprint_same(src_data->file_fp, path, 1);
	project_become_undo();

	/*
	 * Don't bother differencing the file for the project
	 * trunk.
	 */
	if (!ppp->parent)
	{
	    str_free(path);
	    continue;
	}

	/*
	 * build the path to the difference file
	 */
	path_d = str_format("%S,D", path);
	trace_string(path_d->str_text);

	/*
	 * Run the difference command.	All newly create
	 * files are differenced against /dev/null, and
	 * every file in this change is a new file.
	 */
	original = str_from_c("/dev/null");
	change_run_diff_command(cp, pup1, original, path, path_d);
	str_free(original);

	/*
	 * Record the fingerprint of the difference file.
	 */
	if (!src_data->diff_file_fp)
	    src_data->diff_file_fp = fingerprint_type.alloc();
	user_become(pup1);
	change_fingerprint_same(src_data->diff_file_fp, path_d, 1);
	user_become_undo();
	str_free(path);
	str_free(path_d);
    }
    user_free(pup1);

    /*
     * build history files
     */
    for (j = 0;; ++j)
    {
	fstate_src_ty   *c_src_data;
	fstate_src_ty   *p_src_data;

	c_src_data = change_file_nth(cp, j);
	if (!c_src_data)
	    break;
	p_src_data =
	    project_file_find(ppp, c_src_data->file_name, view_path_none);
	assert(p_src_data);

	/*
	 * create a new history file
	 */
	change_run_history_create_command(cp, c_src_data);

	/*
	 * Extract the version number from the history file.
	 * Record it in the project and in the change.
	 */
	assert(c_src_data->edit);
	assert(c_src_data->edit->revision);
	p_src_data->edit = history_version_copy(c_src_data->edit);
	p_src_data->edit_origin = history_version_copy(c_src_data->edit);

	/*
	 * Don't set edit number origin in change file state
	 * for created files.
	 * Why not?  Because it only means something for branches
	 * once the change is integrated.
	 */
    }

    /*
     * some more history
     */
    str_free(cstate_data->integration_directory);
    cstate_data->integration_directory = 0;
    str_free(cstate_data->development_directory);
    cstate_data->development_directory = 0;
    chp = change_history_new(cp, up);
    chp->what = cstate_history_what_integrate_pass;
    cstate_data->delta_number = 1;
    project_history_new(ppp, cstate_data->delta_number, change_number);
    cstate_data->state = cstate_state_completed;

    /*
     * write the project state
     *	    (the trunk change state is implicitly written)
     *
     * Write each of the branch states.	 You must write each one
     * AFTER the next branch down has been created, because
     * creating a branch alters the pstate of the one above.
     */
    project_pstate_write(pp[1]);
    for (j = 0; j < new_version_number_length; ++j)
	project_pstate_write(version_pp[j]);
    change_cstate_write(cp);
    gonzo_gstate_write();

    /*
     * release locks
     */
    commit();
    lock_release();

    /*
     * verbose success message
     */
    project_verbose(pp[1], 0, i18n("new release complete"));
    for (j = 0; j < new_version_number_length; ++j)
	project_verbose(version_pp[j], 0, i18n("new release complete"));
    str_free(project_name[0]);
    project_free(pp[0]);
    str_free(project_name[1]);
    project_free(pp[1]);
    change_free(cp);
    user_free(up);
    user_free(pup);
    trace(("}\n"));
}


void
new_release(void)
{
    static arglex_dispatch_ty dispatch[] =
    {
	{arglex_token_help, new_release_help, },
	{arglex_token_list, new_release_list, },
    };

    trace(("new_release()\n{\n"));
    arglex_dispatch(dispatch, SIZEOF(dispatch), new_release_main);
    trace(("}\n"));
}
