// // aegis - project change supervisor // Copyright (C) 1991-2009, 2011, 2012, 2014 Peter Miller // Copyright (C) 2007-2009 Walter Franzini // // 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 void project::convert_to_new_format(void) { trace(("project::convert_to_new_format(this = %p)\n{\n", this)); // Don't worry about is not being NULL, it won't contain much, // and it will only be a memory leak when the project is first // converted, and will never happen again. pcp = change::create(this, TRUNK_CHANGE_NUMBER); pcp->bind_new(); change_branch_new(pcp); // // The new change is in the 'being developed' // state, and will be forever. // assert(up); cstate_history_ty *h = pcp->history_new(up); h->what = cstate_history_what_new_change; h = pcp->history_new(up); h->what = cstate_history_what_develop_begin; pcp->cstate_get()->state = cstate_state_being_developed; // // set the development directory // pcp->development_directory_set(home_path_get()); // // Copy the information from the old format of project state // into the newly created change. Remember to clear the old // stuff out, so that it isn't written back to the file. // pstate_get(); change_branch_umask_set(pcp, pstate_data->umask); pstate_data->umask = 0; if (pstate_data->owner_name) { // this field is ignored str_free(pstate_data->owner_name); pstate_data->owner_name = 0; } if (pstate_data->group_name) { // this field is ignored str_free(pstate_data->group_name); pstate_data->group_name = 0; } if (pstate_data->description) { project_description_set(this, pstate_data->description); str_free(pstate_data->description); pstate_data->description = 0; } change_branch_developer_may_review_set ( pcp, pstate_data->developer_may_review ); pstate_data->developer_may_review = false; change_branch_developer_may_integrate_set ( pcp, pstate_data->developer_may_integrate ); pstate_data->developer_may_integrate = false; change_branch_reviewer_may_integrate_set ( pcp, pstate_data->reviewer_may_integrate ); pstate_data->reviewer_may_integrate = false; change_branch_developers_may_create_changes_set ( pcp, pstate_data->developers_may_create_changes ); pstate_data->developers_may_create_changes = false; // // Old-style projects alawys had regression tests exempt by default. // change_branch_default_test_regression_exemption_set(pcp, true); change_branch_forced_develop_begin_notify_command_set ( pcp, pstate_data->forced_develop_begin_notify_command ); pstate_data->forced_develop_begin_notify_command = 0; change_branch_develop_end_notify_command_set ( pcp, pstate_data->develop_end_notify_command ); pstate_data->develop_end_notify_command = 0; change_branch_develop_end_undo_notify_command_set ( pcp, pstate_data->develop_end_undo_notify_command ); pstate_data->develop_end_undo_notify_command = 0; change_branch_review_pass_notify_command_set ( pcp, pstate_data->review_pass_notify_command ); pstate_data->review_pass_notify_command = 0; change_branch_review_pass_undo_notify_command_set ( pcp, pstate_data->review_pass_undo_notify_command ); pstate_data->review_pass_undo_notify_command = 0; change_branch_review_fail_notify_command_set ( pcp, pstate_data->review_fail_notify_command ); pstate_data->review_fail_notify_command = 0; change_branch_integrate_pass_notify_command_set ( pcp, pstate_data->integrate_pass_notify_command ); pstate_data->integrate_pass_notify_command = 0; change_branch_integrate_fail_notify_command_set ( pcp, pstate_data->integrate_fail_notify_command ); pstate_data->integrate_fail_notify_command = 0; change_branch_default_development_directory_set ( pcp, pstate_data->default_development_directory ); pstate_data->default_development_directory = 0; change_branch_default_test_exemption_set ( pcp, pstate_data->default_test_exemption ); pstate_data->default_test_exemption = false; if (!pstate_data->copyright_years) { pstate_data->copyright_years = (pstate_copyright_years_list_ty *) pstate_copyright_years_list_type.alloc(); } for (size_t j = 0; j < pstate_data->copyright_years->length; ++j) { change_copyright_year_append ( pcp, pstate_data->copyright_years->list[j] ); } pstate_copyright_years_list_type.free(pstate_data->copyright_years); pstate_data->copyright_years = 0; // no explict next change number in new format pstate_data->next_change_number = 0; // no explict next delta number in new format pstate_data->next_delta_number = 0; if (!pstate_data->src) { pstate_data->src = (pstate_src_list_ty *)pstate_src_list_type.alloc(); } for (size_t j = 0; j < pstate_data->src->length; ++j) { pstate_src_ty *sp1 = pstate_data->src->list[j]; if (!sp1->file_name) { sub_context_ty *scp; yuck: scp = sub_context_new(); sub_var_set_string(scp, "File_Name", pstate_path); sub_var_set_charstar(scp, "FieLD_Name", "src"); project_fatal ( this, scp, i18n("$filename: corrupted \"$field_name\" field") ); // NOTREACHED sub_context_delete(scp); } fstate_src_ty *sp2 = pcp->file_new(sp1->file_name); if (!(sp1->mask & pstate_src_usage_mask)) goto yuck; sp2->usage = sp1->usage; sp2->mask |= fstate_src_usage_mask; sp2->action = file_action_create; sp2->mask |= fstate_src_action_mask; if (sp1->edit_number) { sp2->edit = (history_version_ty *)history_version_type.alloc(); sp2->edit->revision = str_copy(sp1->edit_number); sp2->edit_origin = (history_version_ty *)history_version_type.alloc(); sp2->edit_origin->revision = str_copy(sp1->edit_number); } sp2->locked_by = sp1->locked_by; sp2->about_to_be_created_by = sp1->about_to_be_created_by; sp2->deleted_by = sp1->deleted_by; if (sp2->deleted_by) sp2->action = file_action_remove; // // This code must agree with the corresponding code in // libaegis/change/file/fstate.c // switch (sp2->action) { case file_action_remove: case file_action_transparent: break; case file_action_create: case file_action_modify: case file_action_insulate: #ifndef DEBUG default: #endif if (sp2->about_to_be_created_by || sp2->about_to_be_copied_by) sp2->action = file_action_transparent; break; } } pstate_src_list_type.free(pstate_data->src); pstate_data->src = 0; if (!pstate_data->history) { pstate_data->history = (pstate_history_list_ty *)pstate_history_list_type.alloc(); } for (size_t j = 0; j < pstate_data->history->length; ++j) { pstate_history_ty *hp; hp = pstate_data->history->list[j]; change_branch_history_new ( pcp, hp->delta_number, hp->change_number, 0, TIME_NOT_SET, false ); } pstate_history_list_type.free(pstate_data->history); pstate_data->history = 0; if (!pstate_data->change) { pstate_data->change = (pstate_change_list_ty *)pstate_change_list_type.alloc(); } for (size_t j = 0; j < pstate_data->change->length; ++j) { change_branch_change_add(pcp, pstate_data->change->list[j], 0); } pstate_change_list_type.free(pstate_data->change); pstate_data->change = 0; // // copy the staff across // pstate_get(); if (!pstate_data->administrator) { pstate_data->administrator = (pstate_administrator_list_ty *) pstate_administrator_list_type.alloc(); } for (size_t j = 0; j < pstate_data->administrator->length; ++j) { change_branch_administrator_add ( pcp, pstate_data->administrator->list[j] ); } pstate_administrator_list_type.free(pstate_data->administrator); pstate_data->administrator = 0; if (!pstate_data->developer) { pstate_data->developer = (pstate_developer_list_ty *)pstate_developer_list_type.alloc(); } for (size_t j = 0; j < pstate_data->developer->length; ++j) { change_branch_developer_add(pcp, pstate_data->developer->list[j]); } pstate_developer_list_type.free(pstate_data->developer); pstate_data->developer = 0; if (!pstate_data->reviewer) { pstate_data->reviewer = (pstate_reviewer_list_ty *)pstate_reviewer_list_type.alloc(); } for (size_t j = 0; j < pstate_data->reviewer->length; ++j) { change_branch_reviewer_add(pcp, pstate_data->reviewer->list[j]); } pstate_reviewer_list_type.free(pstate_data->reviewer); pstate_data->reviewer = 0; if (!pstate_data->integrator) { pstate_data->integrator = (pstate_integrator_list_ty *)pstate_integrator_list_type.alloc(); } for (size_t j = 0; j < pstate_data->integrator->length; ++j) { change_branch_integrator_add(pcp, pstate_data->integrator->list[j]); } pstate_integrator_list_type.free(pstate_data->integrator); pstate_data->integrator = 0; if (pstate_data->currently_integrating_change) { change_current_integration_set ( pcp, pstate_data->currently_integrating_change ); pstate_data->currently_integrating_change = 0; } // // These should actually be acted on to create a change tree, // but that will have to wait for a future aegis change, when // branching is working properly. // // pstate_data->version_major = 0; // pstate_data->version_minor = 0; // if (pstate_data->version_previous) { pcp->cstate_get()->version_previous = pstate_data->version_previous; pstate_data->version_previous = 0; } // // By default we reuse change numbers. // change_branch_reuse_change_numbers_set(pcp, 1); // // Phew! Who would ever have guessed there was so much to do // when it came to converting a project to use branching? // trace(("}\n")); } project::~project() { trace(("project::~project(this = %p)\n{\n", this)); if (pcp) pcp.reset(); assert(name); str_free(name); if (home_path) str_free(home_path); if (baseline_path_unresolved) str_free(baseline_path_unresolved); if (baseline_path) str_free(baseline_path); if (change2time_stp) itab_free(change2time_stp); if (history_path) str_free(history_path); if (info_path) str_free(info_path); if (pstate_path) str_free(pstate_path); if (changes_path) str_free(changes_path); if (pstate_data) pstate_type.free(pstate_data); if (parent) parent->free(); file_list_invalidate(); trace(("}\n")); } project::project(string_ty *s) : reference_count(1), name(str_copy(s)), home_path(0), baseline_path_unresolved(0), baseline_path(0), history_path(0), info_path(0), pstate_path(0), changes_path(0), pstate_data(0), is_a_new_file(false), lock_magic(0), pcp(), uid(-1), gid(-1), parent(0), parent_bn(0), off_limits(false) { change2time_stp = itab_alloc(); for (size_t j = 0; j < SIZEOF(file_list); ++j) file_list[j] = 0; for (size_t k = 0; k < SIZEOF(file_by_uuid); ++k) file_by_uuid[k] = 0; } project * project_alloc(string_ty *s) { trace(("project_alloc(s = \"%s\")\n{\n", s->str_text)); project *pp = new project(s); trace(("return %p;\n", pp)); trace(("}\n")); return pp; } project * project_copy(project *pp) { trace(("project_copy(pp = %p)\n", pp)); return pp->copy(); } project * project::copy(void) { trace(("project::copy(this = %p)\n{\n", this)); assert(this->reference_count >= 1); ++reference_count; trace(("return %p;\n", this)); trace(("}\n")); return this; } void project_free(project *pp) { trace(("project_free(pp = %p)\nn", pp)); pp->free(); } void project::free() { trace(("project::free(this = %p)\n{\n", this)); assert(reference_count >= 1); reference_count--; // // The root project's pcp references the root project (project // data structure). Thus even as all other references go away, the // reference count of the root project remains at 1 and its memory // is not released. In the special case where reference_count is // one in project::free() we should test whether we're the root // project and the only thing holding on to us is the pcp. In that // case we should untie the loop and do a free on the pcp (which // will cascade to free us). // if (reference_count == 1) { change::pointer tmp = change_get_raw(); if (tmp && tmp->project_get() == this && is_a_trunk()) change_reset(); } else if (this->reference_count <= 0) { delete this; } trace(("}\n")); } void project_pstate_lock_prepare_top(project *pp) { trace(("project_pstate_lock_prepare_top(pp = %p)\n{\n", pp)); pp->trunk_get()->pstate_lock_prepare(); trace(("}\n")); } static void waiting_for_baseline_read_lock(void *p) { project *pp; pp = (project *)p; if (user_ty::create()->lock_wait()) project_error(pp, 0, i18n("waiting for baseline read lock")); else project_fatal(pp, 0, i18n("baseline read lock not available")); } void project_baseline_read_lock_prepare(project *pp) { // // A branch's baseline is "unioned" with its parent's // baseline, so we need to lock them as well - all the // way up the tree. // trace(("project_baseline_read_lock_prepare(pp = %p)\n{\n", pp)); assert(pp); for (;;) { lock_prepare_baseline_read ( pp->name_get(), waiting_for_baseline_read_lock, pp ); if (pp->is_a_trunk()) break; pp = pp->parent_get(); } trace(("}\n")); } static void waiting_for_baseline_write_lock(void *p) { project *pp; pp = (project *)p; if (user_ty::create()->lock_wait()) project_error(pp, 0, i18n("waiting for baseline write lock")); else project_fatal(pp, 0, i18n("baseline write lock not available")); } void project_baseline_write_lock_prepare(project *pp) { trace(("project_baseline_write_lock_prepare(pp = %p)\n{\n", pp)); lock_prepare_baseline_write ( pp->name_get(), waiting_for_baseline_write_lock, pp ); trace(("}\n")); } static void waiting_for_history_lock(void *p) { project *pp; pp = (project *)p; if (user_ty::create()->lock_wait()) project_error(pp, 0, i18n("waiting for history lock")); else project_fatal(pp, 0, i18n("history lock not available")); } void project_history_lock_prepare(project *pp) { // // The history tool may have no locking of its own, and it will // be ignored if it does. Therefore, take the history lock in // the deepest project. This prevents aeip on different // branches from trashing each other's history files. // trace(("project_history_lock_prepare(pp = %p)\n{\n", pp)); pp = pp->trunk_get(); lock_prepare_history(pp->name_get(), waiting_for_history_lock, pp); trace(("}\n")); } int break_up_version_string(const char *sp, long *buf, size_t buflen_max, size_t &buflen, bool leading_punct) { if (!sp || *sp == 0) return -1; for (;;) { // // one leading punctuation character // but we don't care what it is (- and . are common) // {C locale} // if (leading_punct) { if (!ispunct((unsigned char)*sp)) return -1; ++sp; } else leading_punct = true; // // the next one must be a digit // if (!isdigit((unsigned char)*sp)) return -1; // // The version string must not be too long. (Even // oracle, who use the most unbelievable 6 part version // strings, will cope if you use a big enough buffer.) // if (buflen >= buflen_max) return -1; // // collect the number // long n = 0; for (;;) { n = n * 10 + *sp++ - '0'; if (!isdigit((unsigned char)*sp)) break; } buf[buflen++] = magic_zero_encode(n); // // stop at end of string // (note: trailing punctuation is illegal) // if (!*sp) break; } return 0; } void extract_version_from_project_name(string_ty **name_p, long *buf, size_t buflen_max, size_t &buflen) { string_ty *name; char *sp; int err; // // if the project name does not end in a digit, // it can't end in a version string // name = *name_p; if ( name->str_length < 3 || // C locale !isdigit((unsigned char)name->str_text[name->str_length - 1]) ) return; sp = name->str_text; // // move down the name, looking for a trailing version number // for (; *sp; ++sp) { // // skip over leading non-punctuation // {C locale} // if (!ispunct((unsigned char)*sp)) continue; // // attempt to break up the rest of the string // err = break_up_version_string(sp, buf, buflen_max, buflen, true); // // if there was an error, try again further down // if (err) continue; // // the version number is now in the buffer, // so shorten the name to match // *name_p = str_n_from_c(name->str_text, sp - name->str_text); str_free(name); return; } } void extract_version_from_project_name(nstring &name, long *buf, size_t buflen_max, size_t &buflen) { // // if the project name does not end in a digit, // it can't end in a version string // if ( name.size() < 3 || // C locale !isdigit((unsigned char)name.back()) ) return; // // move down the name, looking for a trailing version number // for (const char *sp = name.c_str(); *sp; ++sp) { // // skip over leading non-punctuation // {C locale} // if (!ispunct((unsigned char)*sp)) continue; // // attempt to break up the rest of the string // int err = break_up_version_string(sp, buf, buflen_max, buflen, true); // // if there was an error, try again further down // if (err) continue; // // the version number is now in the buffer, // so shorten the name to match // name = name.substr(0, sp - name.c_str()); return; } } project * project::find_branch(const char *version_string) { if (parent) { // // As a special case, the "grandparent" of a change is // repesented by a branch name of ".." as this is expected to be // the commonest variety of cross branch merge. // if (!strcmp(version_string, "..")) return project_copy(parent); // // the version is relative to the trunk of the project // return trunk_get()->find_branch(version_string); } // // break the version string // long version[20]; size_t version_length = 0; if ( *version_string && break_up_version_string ( version_string, version, SIZEOF(version), version_length, false ) ) { sub_context_ty *scp; scp = sub_context_new(); sub_var_set_charstar(scp, "Number", version_string); fatal_intl(scp, i18n("bad version $number")); // NOTREACHED sub_context_delete(scp); } if (version_length == 0) return project_copy(this); // // follow the branches down // project *pp = this; for (size_t j = 0; j < version_length; ++j) { long cn = version[j]; change::pointer cp = change::create(pp, cn); cp->bind_existing(); if (!cp->was_a_branch()) { sub_context_ty *scp; scp = sub_context_new(); sub_var_set_charstar(scp, "Number", version_string); change_fatal(cp, scp, i18n("version $number not a branch")); // NOTREACHED sub_context_delete(scp); } pp = pp->bind_branch(cp); } // // return the derived project // return pp; } void project::pstate_write(void) { string_ty *filename; string_ty *filename_new; string_ty *filename_old; static int count; int compress; trace(("project::pstate_write(thid = %p)\n{\n", this)); // // write out the associated change, if it was read in // if (pcp) pcp->cstate_write(); // // write it out // compress = project_compress_database_get(this); if (pstate_data) { filename = pstate_path_get(); filename_new = str_format("%s,%d", filename->str_text, ++count); filename_old = str_format("%s,%d", filename->str_text, ++count); user_ty::become scoped(get_user()); // enums with 0 value not to be printed type_enum_option_set(); if (is_a_new_file) { undo_unlink_errok(filename_new); pstate_write_file(filename_new, pstate_data, compress); commit_rename(filename_new, filename); } else { undo_unlink_errok(filename_new); pstate_write_file(filename_new, pstate_data, compress); commit_rename(filename, filename_old); commit_rename(filename_new, filename); commit_unlink_errok(filename_old); } // // Change so the project owns it. // (Only needed for new files, but be paranoid.) // os_chmod(filename_new, 0644 & ~project_umask_get(this)); str_free(filename_new); str_free(filename_old); } trace(("}\n")); } void project_pstate_write_top(project *pp) { trace(("project_pstate_write_top(pp = %p)\n{\n", pp)); pp->trunk_get()->pstate_write(); trace(("}\n")); } string_ty * project_Home_path_get(project *pp) { return pp->trunk_get()->home_path_get(); } nstring project::top_path_get(bool resolve) { return change_get()->top_path_get(resolve); } nstring project_rss_path_get(project *pp, bool resolve) { return os_path_cat(pp->top_path_get(resolve), "rss"); } void project_error(project *pp, sub_context_ty *scp, const char *s) { string_ty *msg; int need_to_delete; if (!scp) { scp = sub_context_new(); need_to_delete = 1; } else need_to_delete = 0; // // form the message // subst_intl_project(scp, pp); msg = subst_intl(scp, s); // // pass the message to the error function // // re-use substitution context sub_var_set_string(scp, "MeSsaGe", msg); str_free(msg); subst_intl_project(scp, pp); error_intl(scp, i18n("project \"$project\": $message")); if (need_to_delete) sub_context_delete(scp); } void project_fatal(project *pp, sub_context_ty *scp, const char *s) { string_ty *msg; int need_to_delete; if (!scp) { scp = sub_context_new(); need_to_delete = 1; } else need_to_delete = 0; // // form the message // subst_intl_project(scp, pp); msg = subst_intl(scp, s); // // pass the message to the error function // // re-use substitution context sub_var_set_string(scp, "MeSsaGe", msg); str_free(msg); subst_intl_project(scp, pp); fatal_intl(scp, i18n("project \"$project\": $message")); // NOTREACHED if (need_to_delete) sub_context_delete(scp); } void project_verbose(project *pp, sub_context_ty *scp, const char *s) { string_ty *msg; int need_to_delete; if (!scp) { scp = sub_context_new(); need_to_delete = 1; } else need_to_delete = 0; // // form the message // subst_intl_project(scp, pp); msg = subst_intl(scp, s); // // pass the message to the error function // // re-use the substitution context sub_var_set_string(scp, "MeSsaGe", msg); str_free(msg); subst_intl_project(scp, pp); verbose_intl(scp, i18n("project \"$project\": $message")); if (need_to_delete) sub_context_delete(scp); } void project_change_append(project *pp, long cn, int is_a_branch) { change::pointer cp = pp->change_get(); change_branch_change_add(cp, cn, is_a_branch); } void project_change_delete(project *pp, long cn) { change::pointer cp = pp->change_get(); change_branch_change_remove(cp, cn); } int project_change_number_in_use(project *pp, long cn) { change::pointer cp = pp->change_get(); return change_branch_change_number_in_use(cp, cn); } nstring project_version_short_get(project *pp) { nstring s; trace(("project_version_short_get(pp = %p)\n{\n", pp)); if (!pp->is_a_trunk()) { assert(pp->parent_branch_number_get() > 0 || pp->parent_branch_number_get() == MAGIC_ZERO); s = project_version_short_get(pp->parent_get()); if (s.length()) { // ...punctuation? s = nstring::format ( "%s.%ld", s.c_str(), magic_zero_decode(pp->parent_branch_number_get()) ); } else { s = nstring::format ( "%ld", magic_zero_decode(pp->parent_branch_number_get()) ); } } else { pp->change_get(); // make sure is in memory pstate_ty *pstate_data = pp->pstate_get(); if (pstate_data->version_major || pstate_data->version_minor) { // // old style project, not yet branching // s = nstring::format ( "%ld.%ld", pstate_data->version_major, pstate_data->version_minor ); } else { assert(s.empty()); } } trace(("return \"%s\";\n", s.c_str())); trace(("}\n")); return s; } nstring project_version_get(project *pp) { trace(("project_version_get(pp = %p)\n{\n", pp)); change::pointer cp = pp->change_get(); long dn = change_history_delta_latest(cp); nstring tmp = project_version_short_get(pp); nstring result = nstring::format("%s.D%3.3ld", tmp.c_str(), dn); trace(("return \"%s\";\n", result.c_str())); trace(("}\n")); return result; } user_ty::pointer project_user(project *pp) { return pp->get_user(); } user_ty::pointer project::get_user(void) const { trace(("project::get_user(this = %p)\n{\n", this)); if (!up) { fatal_raw ( "%s: %d: project::up not set, you should have called " "project_bind_existing or project::bind_new() before now " "(bug)", __FILE__, __LINE__ ); } trace(("return %p;\n", up.get())); trace(("}\n")); return up; } void project_become(project *pp) { trace(("project_become(pp = %p)\n{\n", pp)); pp->get_user()->become_begin(); trace(("}\n")); } void project_become_undo(project *pp) { trace(("project_become_undo(cp)\n{\n")); pp->get_user()->become_end(); trace(("}\n")); } int project_is_readable(project *pp) { trace(("%s\n", __PRETTY_FUNCTION__)); pp = pp->trunk_get(); string_ty *s = pp->pstate_path_get(); os_become_orig(); int err = os_readable(s); os_become_undo(); trace(("return %d\n", err)); return err; } long project_next_test_number_get(project *pp) { pstate_ty *pstate_data; long result; int skip; trace(("project_next_test_number_get(pp = %p)\n{\n", pp)); skip = project_skip_unlucky_get(pp); pp = pp->trunk_get(); pstate_data = pp->pstate_get(); result = pstate_data->next_test_number; if (skip) result = skip_unlucky(result); pstate_data->next_test_number = result + 1; trace(("return %ld;\n", result)); trace(("}\n")); return result; } long project_minimum_change_number_get(project *pp) { change::pointer cp = pp->change_get(); return change_branch_minimum_change_number_get(cp); } void project_minimum_change_number_set(project *pp, long n) { change::pointer cp = pp->change_get(); change_branch_minimum_change_number_set(cp, n); } bool project_reuse_change_numbers_get(project *pp) { change::pointer cp = pp->change_get(); return change_branch_reuse_change_numbers_get(cp); } void project_reuse_change_numbers_set(project *pp, bool n) { change::pointer cp = pp->change_get(); change_branch_reuse_change_numbers_set(cp, n); } long project_minimum_branch_number_get(project *pp) { change::pointer cp = pp->change_get(); return change_branch_minimum_branch_number_get(cp); } void project_minimum_branch_number_set(project *pp, long n) { change::pointer cp = pp->change_get(); change_branch_minimum_branch_number_set(cp, n); } bool project_skip_unlucky_get(project *pp) { change::pointer cp = pp->change_get(); return change_branch_skip_unlucky_get(cp); } void project_skip_unlucky_set(project *pp, bool n) { change::pointer cp = pp->change_get(); change_branch_skip_unlucky_set(cp, n); } static bool project_name_ok(const char *sp) { // // The horrible characters are file separators in a variety of // operating systems (unix, dos, mac, primos). // static char horrible[] = "/\\:>"; if (!*sp) return 0; for (; *sp; ++sp) { // C locale if ( !isprint((unsigned char)*sp) || isspace((unsigned char)*sp) || strchr(horrible, *sp) ) return false; } return true; } int project_name_ok(string_ty *s) { return project_name_ok(s->str_text); } bool project_name_ok(const nstring &s) { return project_name_ok(s.c_str()); } // vim: set ts=8 sw=4 et :