// file : odb/relational/changelog.cxx // copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file #include #include #include #include #include #include using namespace std; namespace relational { namespace changelog { using namespace sema_rel; using sema_rel::model; using sema_rel::changelog; typedef map deleted_table_map; typedef map deleted_column_map; namespace { // // diff // struct diff_table: trav_rel::column, trav_rel::primary_key, trav_rel::foreign_key, trav_rel::index { enum mode_type {mode_add, mode_drop}; diff_table (table& o, mode_type m, alter_table& a, graph& gr, options const& op, model_version const* v) : other (o), mode (m), at (a), g (gr), ops (op), version (v) {} virtual void traverse (sema_rel::column& c) { using sema_rel::column; if (mode == mode_add) { if (column* oc = other.find (c.name ())) { if (c.type () != oc->type ()) diagnose_column (c, "type", oc->type (), c.type ()); if (c.null () != oc->null ()) { alter_column& ac (g.new_node (c.id ())); // Set the alters edge. // column* b (at.lookup (c.name ())); assert (b != 0); g.new_edge (ac, *b); ac.null (c.null ()); g.new_edge (at, ac, c.name ()); } if (c.default_ () != oc->default_ ()) diagnose_column ( c, "default value", oc->default_ (), c.default_ ()); if (c.options () != oc->options ()) diagnose_column (c, "options", oc->options (), c.options ()); } else { if (version != 0) { data_member_path const& mp ( c.get ("member-path")); semantics::data_member* m (context::added_member (mp)); if (m != 0) { // Make sure the addition version is the current version. // if (context::added (*m) != version->current) { location l (m->get ("added-location")); error (l) << "member addition version is not the same " << "as the current model version" << endl; throw operation_failed (); } } // Warn about hard additions. If the version is closed, then // we have already seen these warnings so no need to repeat // them. // else if (ops.warn_hard_add () && version->open) { // Issue nicer diagnostics for the direct data member case. // if (mp.size () == 1) { location l (mp.back ()->location ()); warn (l) << "data member is hard-added" << endl; } else { semantics::class_& s ( dynamic_cast ( mp.front ()->scope ())); warn (s.location ()) << "column '" << c.name () << "' " << "in class '" << s.name () << "' is hard-added" << endl; for (data_member_path::const_iterator i (mp.begin ()); i != mp.end (); ++i) { info ((*i)->location ()) << "corresponding hard-" << "added data member could be '" << (*i)->name () << "'" << endl; } } } } add_column& ac (g.new_node (c, at, g)); g.new_edge (at, ac, c.name ()); } } else { if (other.find (c.name ()) == 0) { if (version != 0) { // See if we have an entry for this column in the soft- // deleted map. // deleted_column_map const& dm ( other.get ("deleted-map")); deleted_column_map::const_iterator i (dm.find (c.name ())); if (i != dm.end ()) { if (context::deleted (*i->second) != version->current) { location l (i->second->get ("deleted-location")); error (l) << "member deletion version is not the same " << "as the current model version" << endl; throw operation_failed (); } } // Warn about hard deletions. If the version is closed, then // we have already seen these warnings so no need to repeat // them. // else if (ops.warn_hard_delete () && version->open) { // Here all we have is a column name and a class (object) // or data member (container) corresponding to the table. // if (semantics::class_* s = other.get ("class", 0)) { warn (s->location ()) << "column '" << c.name () << "' " << "in class '" << s->name () << "' is hard-deleted" << endl; } else { data_member_path const& mp ( other.get ("member-path")); warn (mp.back ()->location ()) << "column '" << c.name () << "' in container '" << mp.back ()->name () << "' is hard-deleted" << endl; } } } drop_column& dc (g.new_node (c.id ())); g.new_edge (at, dc, c.name ()); } } } virtual void traverse (sema_rel::primary_key& pk) { using sema_rel::primary_key; if (mode == mode_add) { if (primary_key* opk = other.find (pk.name ())) { if (pk.auto_ () != opk->auto_ ()) diagnose_primary_key (pk, "auto kind"); // Database-specific information. // for (primary_key::extra_map::const_iterator i ( pk.extra ().begin ()); i != pk.extra ().end (); ++i) { if (opk->extra ().count (i->first) == 0 || opk->extra ()[i->first] != i->second) diagnose_primary_key (pk, i->first.c_str ()); } for (primary_key::extra_map::const_iterator i ( opk->extra ().begin ()); i != opk->extra ().end (); ++i) { if (pk.extra ().count (i->first) == 0 || pk.extra ()[i->first] != i->second) diagnose_primary_key (pk, i->first.c_str ()); } if (pk.contains_size () != opk->contains_size ()) diagnose_primary_key (pk, "member set"); for (primary_key::contains_size_type i (0); i != pk.contains_size (); ++i) { sema_rel::contains& c (pk.contains_at (i)); sema_rel::contains& oc (opk->contains_at (i)); if (c.column ().name () != oc.column ().name ()) diagnose_primary_key (pk, "member set"); } } else { location const& l (pk.get ("cxx-location")); error (l) << "adding object id to an existing class is " << "not supported" << endl; info (l) << "consider re-implementing this change by adding " << "a new class with the object id, migrating the data, and " << "deleteing the old class" << endl; throw operation_failed (); } } else { if (other.find (pk.name ()) == 0) { location const& l (other.get ("cxx-location")); error (l) << "deleting object id from an existing class is " << "not supported" << endl; info (l) << "consider re-implementing this change by adding " << "a new class without the object id, migrating the data, " << "and deleteing the old class" << endl; throw operation_failed (); } } } virtual void traverse (sema_rel::foreign_key& fk) { using sema_rel::foreign_key; if (mode == mode_add) { if (foreign_key* ofk = other.find (fk.name ())) { if (fk.deferrable () != ofk->deferrable ()) diagnose_foreign_key (fk, "deferrable mode"); if (fk.on_delete () != ofk->on_delete ()) diagnose_foreign_key (fk, "on delete action"); if (fk.referenced_table () != ofk->referenced_table ()) // See diagnose_foreign_key() if changing this name. // diagnose_foreign_key (fk, "pointed-to class"); if (fk.referenced_columns () != ofk->referenced_columns ()) diagnose_foreign_key (fk, "id member set"); if (fk.contains_size () != ofk->contains_size ()) diagnose_foreign_key (fk, "id member set"); for (foreign_key::contains_size_type i (0); i != fk.contains_size (); ++i) { sema_rel::contains& c (fk.contains_at (i)); sema_rel::contains& oc (ofk->contains_at (i)); if (c.column ().name () != oc.column ().name ()) diagnose_foreign_key (fk, "id member set"); } } else { add_foreign_key& afk (g.new_node (fk, at, g)); g.new_edge (at, afk, fk.name ()); } } else { if (other.find (fk.name ()) == 0) { drop_foreign_key& dfk (g.new_node (fk.id ())); g.new_edge (at, dfk, fk.name ()); } } } virtual void traverse (sema_rel::index& i) { using sema_rel::index; if (mode == mode_add) { if (index* oi = other.find (i.name ())) { if (i.type () != oi->type ()) diagnose_index (i, "type", oi->type (), i.type ()); if (i.method () != oi->method ()) diagnose_index (i, "method", oi->method (), i.method ()); if (i.options () != oi->options ()) diagnose_index (i, "options", oi->options (), i.options ()); if (i.contains_size () != oi->contains_size ()) diagnose_index (i, "member set", "", ""); for (index::contains_size_type j (0); j != i.contains_size (); ++j) { sema_rel::contains& c (i.contains_at (j)); sema_rel::contains& oc (oi->contains_at (j)); if (c.column ().name () != oc.column ().name ()) diagnose_index (i, "member set", "", ""); if (c.options () != oc.options ()) diagnose_index ( i, "member options", oc.options (), c.options ()); } } else { add_index& ai (g.new_node (i, at, g)); g.new_edge (at, ai, i.name ()); } } else { if (other.find (i.name ()) == 0) { drop_index& di (g.new_node (i.id ())); g.new_edge (at, di, i.name ()); } } } void diagnose_column (sema_rel::column& c, char const* name, string const& ov, string const& nv) { table& t (c.table ()); location const& tl (t.get ("cxx-location")); location const& cl (c.get ("cxx-location")); error (cl) << "change to data member results in the change of " << "the corresponding column " << name; if (!ov.empty () || !nv.empty ()) cerr << " (old: '" << ov << "', new: '" << nv << "')"; cerr << endl; error (cl) << "this change is not yet handled automatically" << endl; info (cl) << "corresponding column '" << c.name () << "' " << "originates here" << endl; info (tl) << "corresponding table '" << t.name () << "' " << "originates here" << endl; info (cl) << "consider re-implementing this change by adding " << "a new data member with the desired " << name << ", migrating " << "the data, and deleting the old data member" << endl; throw operation_failed (); } void diagnose_primary_key (sema_rel::primary_key& pk, char const* name) { location const& l (pk.get ("cxx-location")); error (l) << "changing object id " << name << " in an existing " << "class is not supported" << endl; info (l) << "consider re-implementing this change by adding " << "a new class with the desired object id " << name << ", " << "migrating the data, and deleteing the old class" << endl; throw operation_failed (); } void diagnose_foreign_key (sema_rel::foreign_key& fk, char const* name) { // This can be an object pointer or a polymorphic base link. // The latter will trigger this call if we change one base // to another. // using sema_rel::table; using sema_rel::foreign_key; // Polymorphic base link is the first foreign key. // table& t (fk.table ()); table::names_iterator p (t.find (fk.name ())); if (t.extra ()["kind"] == "polymorphic derived object" && (p == t.names_begin () || !(--p)->is_a ())) { location const& l (t.get ("cxx-location")); if (name == string ("pointed-to class")) { error (l) << "changing polymorphic base is not " << "supported" << endl; info (l) << "consider re-implementing this change by adding " << "a new derived class with the desired base, migrating the " << "data, and deleteing the old class" << endl; } else { error (l) << "changing polymorphic base " << name << " is not supported" << endl; info (l) << "consider re-implementing this change by adding " << "a new derived class with the desired " << name << ", " << "migrating the data, and deleteing the old class" << endl; } } else { location const& l (fk.get ("cxx-location")); error (l) << "changing object pointer " << name << " is not " << "supported" << endl; info (l) << "consider re-implementing this change by adding " << "a new object pointer with the desired " << name << ", " << "migrating the data, and deleteing the old pointer" << endl; } throw operation_failed (); } void diagnose_index (sema_rel::index& i, char const* name, string const& ov, string const& nv) { table& t (i.table ()); location const& tl (t.get ("cxx-location")); location const& il (i.get ("cxx-location")); error (il) << "change to index " << name; if (!ov.empty () || !nv.empty ()) cerr << " (old: '" << ov << "', new: '" << nv << "')"; cerr << " is not yet handled automatically" << endl; info (il) << "corresponding index '" << i.name () << "' " << "originates here" << endl; info (tl) << "corresponding table '" << t.name () << "' " << "originates here" << endl; info (il) << "consider re-implementing this change by adding " << "a new index with the desired " << name << " and deleting the " << "old one" << endl; throw operation_failed (); } protected: table& other; mode_type mode; alter_table& at; graph& g; options const& ops; model_version const* version; }; struct diff_model: trav_rel::table { enum mode_type {mode_add, mode_drop}; diff_model (model& o, mode_type m, changeset& s, graph& gr, string const& in, options const& op, model_version const* v) : other (o), mode (m), cs (s), g (gr), in_name (in), ops (op), version (v) {} virtual void traverse (sema_rel::table& t) { using sema_rel::table; if (mode == mode_add) { if (table* ot = other.find (t.name ())) { // See if there are any changes to the table. // alter_table& at (g.new_node (t.id ())); // Set the alters edge for lookup. // table* bt (cs.lookup (t.name ())); assert (bt != 0); alters& ae (g.new_edge (at, *bt)); if (t.options () != ot->options ()) diagnose_table (t, "options", ot->options (), t.options ()); if (ot->extra ()["kind"] != t.extra ()["kind"]) diagnose_table (t, "kind", ot->extra ()["kind"], t.extra ()["kind"]); { trav_rel::table table; trav_rel::unames names; diff_table dtable ( *ot, diff_table::mode_add, at, g, ops, version); table >> names >> dtable; table.traverse (t); } { trav_rel::table table; trav_rel::unames names; diff_table dtable ( t, diff_table::mode_drop, at, g, ops, version); table >> names >> dtable; table.traverse (*ot); } if (!at.names_empty ()) g.new_edge (cs, at, t.name ()); else { g.delete_edge (at, *bt, ae); g.delete_node (at); } } else { // Soft-add is only applicable to containers. // if (version != 0 && t.count ("member-path")) { data_member_path const& mp ( t.get ("member-path")); // Make sure the addition version is the current version. // semantics::data_member* m (context::added_member (mp)); if (m != 0) { if (context::added (*m) != version->current) { location l (m->get ("added-location")); error (l) << "member addition version is not the same " << "as the current model version" << endl; throw operation_failed (); } } // Warn about hard additions. If the version is closed, then // we have already seen these warnings so no need to repeat // them. // else if (ops.warn_hard_add () && version->open) { // Issue nicer diagnostics for the direct data member case. // if (mp.size () == 1) { location l (mp.back ()->location ()); warn (l) << "data member is hard-added" << endl; } else { semantics::class_& s ( dynamic_cast ( mp.front ()->scope ())); warn (s.location ()) << "container table '" << t.name () << "' in class '" << s.name () << "' is " << "hard-added" << endl; for (data_member_path::const_iterator i (mp.begin ()); i != mp.end (); ++i) { info ((*i)->location ()) << "corresponding hard-" << "added data member could be '" << (*i)->name () << "'" << endl; } } } } add_table& at (g.new_node (t, cs, g)); g.new_edge (cs, at, t.name ()); } } else { if (other.find
(t.name ()) == 0) { if (version != 0) { // See if we have an entry for this table in the soft- // deleted map. // deleted_table_map const& dm ( other.get ("deleted-map")); deleted_table_map::const_iterator i (dm.find (t.name ())); if (i != dm.end ()) { // This table could be derived either from a class (object) // or data member (container). // semantics::class_* c ( dynamic_cast (i->second)); if (c != 0 && context::deleted (*c) != version->current) { location l (c->get ("deleted-location")); error (l) << "class deletion version is not the same " << "as the current model version" << endl; throw operation_failed (); } semantics::data_member* m ( dynamic_cast (i->second)); if (m != 0 && context::deleted (*m) != version->current) { location l (m->get ("deleted-location")); error (l) << "member deletion version is not the same " << "as the current model version" << endl; throw operation_failed (); } } // Warn about hard deletions. If the version is closed, then // we have already seen these warnings so no need to repeat // them. // // At first, it may seem like a good idea not to warn about // class deletions since it is not possible to do anything // useful with a class without referencing it from the code // (in C++ sense). However, things get tricky once we consider // polymorphism since the migration code could be "working" // with the hard-deleted derived class via its base. So we // are going to warn about polymorphic derived tables. // else if (ops.warn_hard_delete () && version->open) { string k (t.extra ()["kind"]); // We don't have any sensible location. // if (k == "container") { // We will issue a useless warning if the object table // that this container references is also deleted. // While we could detect this, it is going to require // some effort since we would have to delay the warning // and check later once we know all the deleted tables. // cerr << in_name << ": warning: container table '" << t.name () << "' is hard-deleted" << endl; } else if (k == "polymorphic derived object") { // The same as above: the warning will be useless if // we are dropping the whole hierarchy. // cerr << in_name << ": warning: polymorphic derived " << "object table '" << t.name () << "' is hard-deleted" << endl; } } } drop_table& dt (g.new_node (t.id ())); g.new_edge (cs, dt, t.name ()); } } } void diagnose_table (sema_rel::table& t, char const* name, string const& ov, string const& nv) { location const& tl (t.get ("cxx-location")); error (tl) << "change to object or container member results in " "the change of the corresponding table " << name; if (!ov.empty () || !nv.empty ()) cerr << " (old: '" << ov << "', new: '" << nv << "')"; cerr << endl; error (tl) << "this change is not yet handled automatically" << endl; info (tl) << "consider re-implementing this change by adding a " << "new object or container member with the desired " << name << ", migrating the data, and deleting the old object or member" << endl; throw operation_failed (); } protected: model& other; mode_type mode; changeset& cs; graph& g; string in_name; options const& ops; model_version const* version; }; // Assumes the new model has cxx-location set. If version is not 0, // then assume it is the current model version and the new model is // the current model which has member paths and deleted maps set. // changeset& diff (model& o, model& n, changelog& l, string const& in_name, options const& ops, model_version const* version) { changeset& r (l.new_node (n.version ())); // Set the alters edge for lookup. If we are diff'ing two models of // the same version, then use the old model as a base. Otherwise use // the tip of changelog (it should correspond to the old model). // if (o.version () == n.version ()) l.new_edge (r, o); else { if (l.contains_changeset_empty ()) { model& m (l.model ()); assert (o.version () == m.version ()); l.new_edge (r, m); } else { changeset& c (l.contains_changeset_back ().changeset ()); assert (o.version () == c.version ()); l.new_edge (r, c); } } { trav_rel::model model; trav_rel::qnames names; diff_model dmodel ( o, diff_model::mode_add, r, l, in_name, ops, version); model >> names >> dmodel; model.traverse (n); } { trav_rel::model model; trav_rel::qnames names; diff_model dmodel ( n, diff_model::mode_drop, r, l, in_name, ops, version); model >> names >> dmodel; model.traverse (o); } return r; } // // patch // struct patch_table: trav_rel::add_column, trav_rel::drop_column, trav_rel::alter_column, trav_rel::add_index, trav_rel::drop_index, trav_rel::add_foreign_key, trav_rel::drop_foreign_key { patch_table (table& tl, graph& gr): t (tl), g (gr) {} virtual void traverse (sema_rel::add_column& ac) { try { column& c (g.new_node (ac, t, g)); g.new_edge (t, c, ac.name ()); } catch (duplicate_name const&) { cerr << "error: invalid changelog: column '" << ac.name () << "' already exists in table '" << t.name () << "'" << endl; throw operation_failed (); } } virtual void traverse (sema_rel::drop_column& dc) { table::names_iterator i (t.find (dc.name ())); if (i == t.names_end () || !i->nameable ().is_a ()) { cerr << "error: invalid changelog: column '" << dc.name () << "' does not exist in table '" << t.name () << "'" << endl; throw operation_failed (); } g.delete_edge (t, i->nameable (), *i); } virtual void traverse (sema_rel::alter_column& ac) { if (column* c = t.find (ac.name ())) { if (ac.null_altered ()) c->null (ac.null ()); } else { cerr << "error: invalid changelog: column '" << ac.name () << "' does not exist in table '" << t.name () << "'" << endl; throw operation_failed (); } } virtual void traverse (sema_rel::add_index& ai) { using sema_rel::index; try { index& i (g.new_node (ai, t, g)); g.new_edge (t, i, ai.name ()); } catch (duplicate_name const&) { cerr << "error: invalid changelog: index '" << ai.name () << "' already exists in table '" << t.name () << "'" << endl; throw operation_failed (); } } virtual void traverse (sema_rel::drop_index& di) { using sema_rel::index; table::names_iterator i (t.find (di.name ())); if (i == t.names_end () || !i->nameable ().is_a ()) { cerr << "error: invalid changelog: index '" << di.name () << "' does not exist in table '" << t.name () << "'" << endl; throw operation_failed (); } g.delete_edge (t, i->nameable (), *i); } virtual void traverse (sema_rel::add_foreign_key& afk) { using sema_rel::foreign_key; try { foreign_key& fk (g.new_node (afk, t, g)); g.new_edge (t, fk, afk.name ()); } catch (duplicate_name const&) { cerr << "error: invalid changelog: foreign key '" << afk.name () << "' already exists in table '" << t.name () << "'" << endl; throw operation_failed (); } } virtual void traverse (sema_rel::drop_foreign_key& dfk) { using sema_rel::foreign_key; table::names_iterator i (t.find (dfk.name ())); if (i == t.names_end () || !i->nameable ().is_a ()) { cerr << "error: invalid changelog: foreign key '" << dfk.name () << "' does not exist in table '" << t.name () << "'" << endl; throw operation_failed (); } g.delete_edge (t, i->nameable (), *i); } protected: table& t; graph& g; }; struct patch_model: trav_rel::add_table, trav_rel::drop_table, trav_rel::alter_table { patch_model (model& ml, graph& gr): m (ml), g (gr) {} virtual void traverse (sema_rel::add_table& at) { try { table& t (g.new_node
(at, m, g)); g.new_edge (m, t, at.name ()); } catch (duplicate_name const&) { cerr << "error: invalid changelog: table '" << at.name () << "' already exists in model version " << m.version () << endl; throw operation_failed (); } } virtual void traverse (sema_rel::drop_table& dt) { model::names_iterator i (m.find (dt.name ())); if (i == m.names_end () || !i->nameable ().is_a
()) { cerr << "error: invalid changelog: table '" << dt.name () << "' does not exist in model version " << m.version () << endl; throw operation_failed (); } g.delete_edge (m, i->nameable (), *i); } virtual void traverse (sema_rel::alter_table& at) { if (table* t = m.find
(at.name ())) { trav_rel::alter_table atable; trav_rel::unames names; patch_table ptable (*t, g); atable >> names >> ptable; atable.traverse (at); } else { cerr << "error: invalid changelog: table '" << at.name () << "' does not exist in model version " << m.version () << endl; throw operation_failed (); } } protected: model& m; graph& g; }; model& patch (model& m, changeset& c, graph& g) { model& r (g.new_node (m, g)); trav_rel::changeset changeset; trav_rel::qnames names; patch_model pmodel (r, g); changeset >> names >> pmodel; changeset.traverse (c); r.version (c.version ()); return r; } } cutl::shared_ptr generate (model& m, model_version const& mv, changelog* old, string const& in_name, string const& out_name, options const& ops) { database db (ops.database ()[0]); cutl::shared_ptr cl ( new (shared) changelog (db.string (), ops.schema_name ()[db])); graph& g (*cl); if (old == 0) { // Don't allow changelog initialization if the version is closed. // This will prevent adding new files to an existing object model // with a closed version. // if (!mv.open) { cerr << out_name << ": error: unable to initialize changelog " << "because current version is closed" << endl; throw operation_failed (); } if (!ops.init_changelog ()) cerr << out_name << ": info: initializing changelog with base " << "version " << mv.base << endl; if (mv.base == mv.current) g.new_edge (*cl, g.new_node (m, g)); else { // In this case we have to create an empty model at the base // version and a changeset. We do it this way instead of putting // everything into the base model in order to support adding new // header files to the project. // cerr << out_name << ": warning: base and current versions " << "differ; assuming base model is empty" << endl; model& nm (g.new_node (mv.base)); g.new_edge (*cl, nm); changeset& c (diff (nm, m, *cl, in_name, ops, &mv)); if (!c.names_empty ()) { g.new_edge (c, nm); g.new_edge (*cl, c); } } return cl; } // Get the changelog base and current versions and do some sanity // checks. // version bver (old->model ().version ()); version cver ( old->contains_changeset_empty () ? bver : old->contains_changeset_back ().changeset ().version ()); if (mv.base < bver) { cerr << in_name << ": error: latest changelog base version is " << "greater than model base version" << endl; throw operation_failed (); } if (mv.current < cver) { cerr << in_name << ": error: latest changelog current version is " << "greater than model current version" << endl; throw operation_failed (); } // Build the new changelog. // model& oldm (old->model ()); // Now we have a case with a "real" old model (i.e., non-empty // and with version older than current) as well as zero or more // changeset. // // model* last (&g.new_node (oldm, g)); model* base (bver == mv.base && mv.base != mv.current ? last : 0); if (base != 0) g.new_edge (*cl, *base); for (changelog::contains_changeset_iterator i ( old->contains_changeset_begin ()); i != old->contains_changeset_end (); ++i) { changeset& cs (i->changeset ()); // Don't copy the changeset for the current version. Instead, we // will re-create it from scratch. // if (cs.version () == mv.current) break; model& prev (*last); last = &patch (prev, cs, g); if (base == 0) { if (last->version () == mv.base) { base = last; g.new_edge (*cl, *base); } else if (last->version () > mv.base) { // We have a gap. Plug it with an empty base model. We will // also need to create a new changeset for this step. // base = &g.new_node (mv.base); g.new_edge (*cl, *base); changeset& c (diff (*base, *last, *cl, in_name, ops, 0)); if (!c.names_empty ()) { g.new_edge (c, *base); g.new_edge (*cl, c); } continue; } } // Copy the changeset unless it is below or at our base version. // if (last->version () <= mv.base) continue; changeset& c ( g.new_node ( cs, cl->contains_changeset_empty () ? static_cast (*base) // Cannot be NULL. : cl->contains_changeset_back ().changeset (), g)); g.new_edge (c, prev); g.new_edge (*cl, c); } // If we still haven't found the new base model, then it means it // has version greater than any changeset we have seen. // if (base == 0) { if (mv.base == mv.current) base = &g.new_node (m, g); else { // Fast-forward the latest model to the new base. // base = last; base->version (mv.base); } g.new_edge (*cl, *base); } // If the current version is closed, make sure the model hasn't // changed. // if (!mv.open) { // If the last changeset has the current version, then apply it. // model* om (last); if (!old->contains_changeset_empty ()) { changeset& c (old->contains_changeset_back ().changeset ()); if (c.version () == mv.current) om = &patch (*last, c, g); } changeset& c (diff (*om, m, *cl, in_name, ops, &mv)); if (!c.names_empty ()) { qnames& n (*c.names_begin ()); cerr << out_name << ": error: current version is closed" << endl; cerr << out_name << ": info: first new change is " << n.nameable ().kind () << " '" << n.name () << "'" << endl; throw operation_failed (); } } // Add a changeset for the current version unless it is the same // as the base version. // if (mv.base != mv.current) { changeset& c (diff (*last, m, *cl, in_name, ops, &mv)); if (!c.names_empty ()) { g.new_edge (c, *last); g.new_edge (*cl, c); } } return cl; } } }