/*
 *	aegis - project change supervisor
 *	Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999 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 <aenbr.h>
#include <aenrls.h>
#include <arglex2.h>
#include <change_bran.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_hist.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));

static void
new_release_usage()
{
	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));

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


static void new_release_list _((void));

static void
new_release_list()
{
	arglex();
	while (arglex_token != arglex_token_eoln)
		generic_argument(new_release_usage);
	list_projects(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 *, dir_walk_message_ty, string_ty *,
	struct stat *));

static void
copy_tree_callback(arg, message, path, st)
	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"/*}*/, arg, message, path, 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));

static void
new_release_main()
{
	sub_context_ty	*scp;
	string_ty	*ip;
	string_ty	*bl;
	string_ty	*hp;
	size_t		j, k;
	pstate		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	chp;
	cstate		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"/*}*/));
	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(scp, "Name1", "%s", arglex_token_name(arglex_token_major));
			sub_var_set(scp, "Name2", "%s", 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(scp, "Name1", "%s", arglex_token_name(arglex_token_minor));
			sub_var_set(scp, "Name2", "%s", 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]))
	{
		scp = sub_context_new();
		sub_var_set(scp, "Name", "%S", project_name);
		fatal_intl(scp, i18n("bad project $name"));
		/* NOTREACHED */
		sub_context_delete(scp);
	}

	/*
	 * 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(scp, "Number", "%S", 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)
		{
			scp = sub_context_new();
			sub_var_set(scp, "Name", "%S", project_name[1]);
			sub_var_set(scp, "Number", "%d", (int)(project_name[1]->str_length - max));
			sub_var_optional(scp, "Number");
			fatal_intl(scp, i18n("project \"$name\" too long"));
			/* NOTREACHED */
			sub_context_delete(scp);
		}
		home = str_format("%S/%S", s2, project_name[1]);
		str_free(s2);

		scp = sub_context_new();
		sub_var_set(scp, "File_Name", "%S", 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 = new_branch_internals(up, ppp, 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(scp, "Name", "%S", 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	p_src_data;
		fstate_src	p1_src_data;
		fstate_src	c_src_data;

		p_src_data = project_file_nth(pp[0], j);
		if (!p_src_data)
			break;
		if (p_src_data->deleted_by)
			continue;
		if (p_src_data->about_to_be_created_by)
			continue;
		if (p_src_data->about_to_be_copied_by)
			continue;

		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	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);
		if (!src_data)
			break;

		/*
		 * generated files are not fingerprinted or differenced
		 */
		if (src_data->usage == file_usage_build)
			continue;

		/*
		 * 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	c_src_data;
		fstate_src	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);
		assert(p_src_data);

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

		/*
		 * Extract the version number from the history file.
		 * Record it in the project and in the change.
		 */
		p_src_data->edit_number =
			change_run_history_query_command
			(
				cp,
				c_src_data->file_name
			);
		p_src_data->edit_number_origin =
			str_copy(p_src_data->edit_number);
		c_src_data->edit_number =
			str_copy(p_src_data->edit_number);
		/*
		 * Don't set edit number origin in change file state
		 * for created files.
		 */
	}

	/*
	 * 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()
{
	trace(("new_release()\n{\n"/*}*/));
	switch (arglex())
	{
	default:
		new_release_main();
		break;

	case arglex_token_help:
		new_release_help();
		break;

	case arglex_token_list:
		new_release_list();
		break;
	}
	trace((/*{*/"}\n"));
}
