//
// aegis - project change supervisor
// Copyright (C) 2001-2008, 2011, 2012, 2014 Peter Miller
//
// 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 3 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, see .
//
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//
// NAME
// import_usage
//
// SYNOPSIS
// void import_usage(void);
//
// DESCRIPTION
// The import_usage function is used to
// briefly describe how to used the `aeimport' command.
//
static void
import_usage(void)
{
const char *progname;
progname = progname_get();
fprintf(stderr, "usage: %s [ ... ]\n", progname);
fprintf(stderr, " %s -Help\n", progname);
quit(1);
}
static void
import_list(void)
{
arglex();
change_identifier cid;
cid.command_line_parse_rest(import_usage);
list_projects(cid, 0);
}
//
// NAME
// import_help
//
// SYNOPSIS
// void import_help(void);
//
// DESCRIPTION
// The import_help function is used to
// describe in detail how to use the 'aegis -RePorT' command.
//
static void
import_help(void)
{
help("aeimport", import_usage);
}
static nstring
work_out_config_name(format_search_list *fslp)
{
nstring s = THE_CONFIG_FILE_NEW;
if (!fslp->query(s))
return s;
for (int j = 1; ; ++j)
{
s = nstring::format("aegis.%d.conf", j);
if (!fslp->query(s))
return s;
}
}
//
// NAME
// import_main
//
// SYNOPSIS
// void import_main(void);
//
// DESCRIPTION
// The import_main function is used to
// import a change in the "being developed" or "being integrated" states.
// It extracts what to do from the command line.
//
static void
import_main(void)
{
sub_context_ty *scp;
project *pp;
user_ty::pointer up;
long version_number[10];
project *version_pp[SIZEOF(version_number)];
pattr_ty *pattr_data;
const char *format_name = 0;
change_set_list_ty *cslp;
project *ppp;
int mode;
trace(("import_main()\n{\n"));
nstring project_name;
nstring home;
size_t version_number_length = 0;
nstring version_string;
nstring source_directory;
while (arglex_token != arglex_token_eoln)
{
switch (arglex_token)
{
default:
generic_argument(import_usage);
continue;
case arglex_token_project:
arglex();
arglex_parse_project(project_name, import_usage);
continue;
case arglex_token_directory:
if (home)
duplicate_option(import_usage);
if (arglex() != arglex_token_string)
{
option_needs_dir(arglex_token_directory, import_usage);
}
//
// To cope with automounters, directories are stored as
// given, or are derived from the home directory in the
// passwd file. Within aegis, pathnames have their
// symbolic links resolved, and any comparison of paths
// is done on this "system idea" of the pathname.
//
home = arglex_value.alv_string;
break;
case arglex_token_version:
if (version_string)
duplicate_option(import_usage);
switch (arglex())
{
default:
option_needs_number(arglex_token_version, import_usage);
case arglex_token_number:
case arglex_token_string:
version_string = arglex_value.alv_string;
break;
case arglex_token_stdio:
version_string.clear();
break;
}
break;
case arglex_token_string:
if (source_directory)
fatal_too_many_files();
source_directory = arglex_value.alv_string;
break;
case arglex_token_format:
if (format_name)
duplicate_option(import_usage);
if (arglex() != arglex_token_string)
{
option_needs_name(arglex_token_format, import_usage);
}
format_name = arglex_value.alv_string;
break;
}
arglex();
}
if (!project_name)
{
error_intl(0, i18n("no project name"));
import_usage();
}
extract_version_from_project_name
(
project_name,
version_number,
SIZEOF(version_number),
version_number_length
);
if (!project_name_ok(project_name.get_ref()))
fatal_bad_project_name(project_name.get_ref());
if (version_string)
{
int err;
err =
break_up_version_string
(
version_string.c_str(),
version_number,
SIZEOF(version_number),
version_number_length,
false
);
if (err)
{
scp = sub_context_new();
sub_var_set_string(scp, "Number", version_string);
fatal_intl(scp, i18n("bad version $number"));
// NOTREACHED
sub_context_delete(scp);
}
}
else if (!version_number_length)
{
version_number[0] = 1;
version_number[1] = 0;
version_number_length = 2;
}
//
// Make sure we understand the file format specified.
//
format::pointer fmt = format::factory(format_name);
// --------------------------------------------------------------
//
// Walk down the source directory tree looking for history files.
//
error_intl(0, i18n("read history files"));
if (!source_directory)
source_directory = ".";
os_become_orig();
format_search_list *fslp = fmt->search(source_directory);
os_become_undo();
//
// It is an error if no history files are found.
//
if (!fslp->length)
fatal_intl(0, i18n("no history files found"));
//
// Extract the list of staff.
//
// This is used to populate the developer, reviewer and integrator
// lists for the project.
//
nstring_list staff;
fslp->staff(staff);
//
// Work out the name to use for the project configuration file.
//
nstring cfg = work_out_config_name(fslp);
// --------------------------------------------------------------
//
// Grope the file version information in order to produce a list
// of change sets.
//
error_intl(0, i18n("find change sets"));
cslp = change_set_find(fslp);
//
// Now set the current time to just before the first change set
// discovered.
//
if (cslp->length)
{
now_set(cslp->item[0]->when - 60);
now_unclearable();
}
// --------------------------------------------------------------
//
// Build some attributes.
//
pattr_data = (pattr_ty *)pattr_type.alloc();
pattr_data->description =
str_format("The \"%s\" program.", project_name.c_str());
pattr_data->reuse_change_numbers = true;
pattr_data->mask |= pattr_reuse_change_numbers_mask;
pattr_data->developers_may_create_changes = true;
pattr_data->mask |= pattr_developers_may_create_changes_mask;
pattr_data->developer_may_review = (staff.size() == 1);
pattr_data->mask |= pattr_developer_may_review_mask;
pattr_data->developer_may_integrate = true;
pattr_data->mask |= pattr_developer_may_integrate_mask;
pattr_data->reviewer_may_integrate = true;
pattr_data->mask |= pattr_reviewer_may_integrate_mask;
pattr_data->minimum_change_number = 10;
pattr_data->mask |= pattr_minimum_change_number_mask;
pattr_data->minimum_branch_number = 1;
pattr_data->mask |= pattr_minimum_branch_number_mask;
pattr_data->default_test_exemption = true;
pattr_data->mask |= pattr_default_test_exemption_mask;
pattr_data->default_test_regression_exemption = true;
pattr_data->mask |= pattr_default_test_regression_exemption_mask;
//
// locate user data
//
up = user_ty::create();
//
// read in the project table
//
gonzo_gstate_lock_prepare_new();
lock_take();
//
// make sure not too privileged
//
if (!up->check_uid())
fatal_user_too_privileged(up->name());
if (!up->check_gid())
fatal_group_too_privileged(up->get_group_name());
//
// it is an error if the name is already in use
//
if (gonzo_alias_to_actual(project_name.get_ref()))
fatal_project_alias_exists(project_name.get_ref());
pp = project_alloc(project_name.get_ref());
pp->bind_new();
//
// The user who ran the command is the project administrator.
//
project_administrator_add(pp, up->name());
//
// Add the staff to the project.
//
// All staff found are automatically authorised as developers,
// reviewers and integrators.
//
// Usually we wouldn't authorise an administrator, except in the
// case where exactly one staff member is found, when it's pretty
// clear who should be an administrator.
//
if (staff.size() == 1 && nstring(staff.front()) != up->name())
project_administrator_add(pp, nstring(staff.front()));
for (size_t j = 0; j < staff.size(); ++j)
{
project_developer_add(pp, nstring(staff[j]));
project_reviewer_add(pp, nstring(staff[j]));
project_integrator_add(pp, nstring(staff[j]));
}
//
// If no project directory was specified
// create the directory in their home directory.
//
if (!home)
{
nstring s1 = up->default_project_directory();
assert(s1);
os_become_orig();
int name_max = os_pathconf_name_max(s1);
os_become_undo();
if ((int)project_name_get(pp).length() > name_max)
fatal_project_name_too_long
(
project_name_get(pp).get_ref(),
name_max
);
home = os_path_cat(s1, nstring(project_name_get(pp)));
project_verbose_directory(pp, home.get_ref());
}
pp->home_path_set(home);
//
// Create the directory and subdirectories.
// It is an error if the directories can't be created.
//
home = nstring(pp->home_path_get());
nstring bl(pp->baseline_path_get());
nstring hp(pp->history_path_get());
nstring ip(pp->info_path_get());
project_become(pp);
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(pp);
//
// add a row to the table
//
gonzo_project_add(pp);
//
// copy the attributes into the project
//
project_pattr_set(pp, pattr_data);
pattr_type.free(pattr_data);
//
// create each of the branches
//
ppp = pp;
for (size_t j = 0; j < version_number_length; ++j)
{
long change_number;
trace(("ppp = %8.8lX\n", (long)ppp));
change_number = magic_zero_encode(version_number[j]);
trace(("change_number = %ld;\n", change_number));
ppp = project_new_branch(ppp, up, change_number);
version_pp[j] = ppp;
}
//
// write the project state
// (the trunk change state is implicitly written)
//
// Write each of the branch state. You must write *after* the
// next branch down is created, because creating a branch alters
// pstate.
//
pp->pstate_write();
for (size_t j = 0; j < version_number_length; ++j)
version_pp[j]->pstate_write();
gonzo_gstate_write();
//
// release locks
//
commit();
lock_release();
//
// verbose success message
//
project_verbose_new_project_complete(pp);
for (size_t j = 0; j < version_number_length; ++j)
project_verbose_new_branch_complete(version_pp[j]);
// --------------------------------------------------------------
//
// Copy all of the files into the history directory.
//
project_error(pp, 0, i18n("copy history files"));
project_become(pp);
mode = 0755 & ~project_umask_get(pp);
for (size_t j = 0; j < fslp->length; ++j)
{
format_search_ty *fsp = fslp->item[j];
nstring tail =
os_below_dir
(
source_directory,
nstring(fsp->filename_physical)
);
nstring sanitized_tail = fmt->sanitize(tail, false);
nstring dst = os_path_cat(nstring(hp), sanitized_tail);
os_mkdir_between(nstring(hp), sanitized_tail, mode);
copy_whole_file(nstring(fsp->filename_physical), dst, true);
//
// Also need to make sure the copied file is unlocked,
// if this format supports the concept of locking.
//
fmt->unlock(dst);
}
project_become_undo(pp);
//
// after this point, we do not use fslp any more
//
delete fslp;
fslp = 0;
//
// After this point, we just use the terminal branch.
//
ppp = pp;
if (version_number_length)
{
ppp = version_pp[version_number_length - 1];
pp->free();
pp = 0;
for (size_t j = 0; j < version_number_length - 1; j++)
{
version_pp[j]->free();
version_pp[j] = 0;
}
}
project_name = project_name_get(ppp);
// ppp = 0;
// --------------------------------------------------------------
//
// The first change creates the project configuration file. This is
// necessary so that we can do all of the commits and produce
// a complete project at the end of the process.
//
config_file(project_name, fmt, now(), cfg);
// experimental
ppp->free();
ppp = 0;
//
// Create a change for each change set
//
for (size_t j = 0; j < cslp->length; ++j)
synthesize(project_name, cslp->item[j]);
// the change set list will not be used below this point, so free
// it here
change_set_list_delete(cslp);
// --------------------------------------------------------------
//
// Check out a copy of each file into the baseline,
// and produce a diff file to go with it.
//
trace(("mark\n"));
reconstruct(project_name);
trace(("}\n"));
}
void
import(void)
{
switch (arglex())
{
default:
import_main();
break;
case arglex_token_help:
import_help();
break;
case arglex_token_list:
import_list();
break;
case arglex_token_version:
version();
break;
}
}
// vim: set ts=8 sw=4 et :