# # patch "commands.cc" # from [ac1b1f473c02fa1f49a295174882fcb8452eb081] # to [c5859590d6dd5b3e9e7a7224ab53a463d04e694a] # # patch "database.cc" # from [fabe0520e53048014dabe606d40e462e846e6993] # to [b140c52f36629793e95c6578f6ccbef219d3e517] # # patch "database.hh" # from [0661609a63093c8c9543a316d0d3f630ee15f762] # to [af2a926539d042f61e97180730ad348ad2181bcd] # # patch "diff_patch.cc" # from [ebe5306eb73976216ea8d862ed6ae2ce0986af88] # to [3255afd06951ac28adafe13c7cea7437c6fd3db7] # # patch "diff_patch.hh" # from [e4e4bf98edbab213f7deab03c90ef306976ebcc8] # to [b94e63ba12f09789dcc416ea61ec621774d25557] # # patch "merge.cc" # from [dc75799326eea15af4fc9cfd65dc3a82f38613d6] # to [506f5389c61de30a65a1d0b4a47a85bf0dd767a5] # # patch "merge.hh" # from [cce807c1a1f9d187eec49d576481e1a7a4647b15] # to [cd2797de2edf9cc40867385e4a8167be98e7ddfa] # # patch "paths.hh" # from [6ae0e9e03232b0919c62bf80814e8d10c7b15883] # to [ea4ee8e37e6c0d93fddd24395ddc82b44e0c9cbb] # # patch "roster.cc" # from [a253a4d916c62272b592bfd0d008abd30dd28f7b] # to [47b7f10128451b5bccc5611487f90e804454a479] # # patch "work.cc" # from [963086ebd20315f2ccfabdf79a69813f77199701] # to [017ae079b4de961c8e2dd41b180b79e04b8c5f6e] # # patch "work.hh" # from [f19c2815fabb8816a73e5907d168e94b271cb639] # to [56b2b9be3b0cb7fcabee21e948788a57658af3eb] # ======================================================================== --- commands.cc ac1b1f473c02fa1f49a295174882fcb8452eb081 +++ commands.cc c5859590d6dd5b3e9e7a7224ab53a463d04e694a @@ -48,6 +48,8 @@ #include "globish.hh" #include "paths.hh" #include "merge.hh" +#include "roster_merge.hh" +#include "roster.hh" // // this file defines the task-oriented "top level" commands which can be @@ -2737,45 +2739,6 @@ } */ -/* -// FIXME_ROSTERS: disabled until rewritten to use rosters - -static void -write_file_targets(change_set const & cs, - update_merge_provider & merger, - app_state & app) -{ - - manifest_map files_to_write; - for (change_set::delta_map::const_iterator i = cs.deltas.begin(); - i != cs.deltas.end(); ++i) - { - file_path pth(delta_entry_path(i)); - file_id ident(delta_entry_dst(i)); - - if (file_exists(pth)) - { - hexenc tmp_id; - calculate_ident(pth, tmp_id, app.lua); - if (tmp_id == ident.inner()) - continue; - } - - P(F("updating %s to %s\n") % pth % ident); - - I(app.db.file_version_exists(ident) - || merger.temporary_store.find(ident) != merger.temporary_store.end()); - - file_data tmp; - if (app.db.file_version_exists(ident)) - app.db.get_file_version(ident, tmp); - else if (merger.temporary_store.find(ident) != merger.temporary_store.end()) - tmp = merger.temporary_store[ident]; - write_localized_data(pth, tmp.inner(), app.lua); - } -} - - // static void dump_change_set(string const & name, // change_set & cs) // { @@ -2784,17 +2747,36 @@ // cout << "change set '" << name << "'\n" << dat << endl; // } +struct update_source + : public file_content_source +{ + std::map & temporary_store; + app_state & app; + update_source (std::map & tmp, + app_state & app) + : temporary_store(tmp), app(app) + {} + void get_file_content(file_id const & fid, + file_data & dat) const + { + std::map::const_iterator i = temporary_store.find(fid); + if (i != temporary_store.end()) + dat = i->second; + else + app.db.get_file_version(fid, dat); + } +}; + CMD(update, N_("working copy"), "", N_("update working copy.\n" "If a revision is given, base the update on that revision. If not,\n" "base the update on the head of the branch (given or implicit)."), OPT_BRANCH_NAME % OPT_REVISION) { - manifest_map m_old, m_ancestor, m_working, m_chosen; - manifest_id m_ancestor_id, m_chosen_id; revision_set r_old, r_working, r_new; - revision_id r_old_id, r_chosen_id; - change_set old_to_chosen, update, remaining; + roster_t old_roster, working_roster, chosen_roster; + marking_map working_mm, chosen_mm, merged_mm; + revision_id r_old_id, r_working_id, r_chosen_id; if (args.size() > 0) throw usage(name); @@ -2804,8 +2786,19 @@ app.require_working_copy(); - calculate_unrestricted_revision(app, r_working, m_old, m_working); + // FIXME: the next few lines are a little bit expensive insofar as they + // load the base roster twice. The API could use some factoring or + // such. But it should work for now; revisit if performance is + // intolerable. + + get_unrestricted_working_revision_and_rosters(app, r_working, + old_roster, + working_roster); + calculate_ident(r_working, r_working_id); + make_roster_for_revision(r_working, r_working_id, + working_roster, working_mm, app); + I(r_working.edges.size() == 1); r_old_id = edge_old_revision(r_working.edges.begin()); @@ -2867,67 +2860,96 @@ % r_chosen_id % app.branch_name); } - app.db.get_revision_manifest(r_chosen_id, m_chosen_id); - app.db.get_manifest(m_chosen_id, m_chosen); + // FIXME_ROSTERS: In the old (pre-roster) code, we supported updating to + // any revision in the graph, anywhere; if there was a path to get there, + // we'd synthesize a changeset to get there. This was a little easier to + // work with in pre-roster monotone because we merged changesets, not + // rosters. + // + // To do this using rosters' default "mark-merge", we'd need to + // synthesize a marking map using a fake ancestry graph. It's + // conceivable, but it's a fair amount of work, and it's not clear that + // many people used it, nor that mark-merge is ideal for the task. It + // might make more sense to make a different merger for this + // case. + // + // Anyways, for the time being we're only implementing "forwards + // updates". That is, you can only "update" to new base revisions which + // are descendents of the base revision you have in your working copy. - calculate_arbitrary_change_set(r_old_id, r_chosen_id, app, old_to_chosen); - - update_merge_provider merger(app, m_old, m_chosen, m_working); + N(is_ancestor(r_old_id, r_chosen_id, app), + F("Update target is not a descendent of working copy base revision\n")); - if (r_working.edges.size() == 0) - { - // working copy has no changes - L(F("updating along chosen edge %s -> %s\n") - % r_old_id % r_chosen_id); - update = old_to_chosen; - } - else - { - change_set - old_to_working(edge_changes(r_working.edges.begin())), - working_to_merged, - chosen_to_merged; + app.db.get_roster(r_chosen_id, chosen_roster, chosen_mm); - L(F("merging working copy with chosen edge %s -> %s\n") - % r_old_id % r_chosen_id); + // Note that, as far as "uncommon ancestors" go, the working's "uncommon + // ancestors" are the same as the base revision's "uncommon ancestors" + // relative to the chosen target: it's an empty set. The only "uncommon + // ancestors" we're really interested in finding are those above the + // chosen target and not above the base revision; so we can use the + // database function here using the base revision id, not the working + // copy revision id. + // + // This will stop being true when we have merge-into-dir support. - // we have the following - // - // old --> working - // | | - // V V - // chosen --> merged - // - // - old is the revision specified in MT/revision - // - working is based on old and includes the working copy's changes - // - chosen is the revision we're updating to and will end up in MT/revision - // - merged is the merge of working and chosen - // - // we apply the working to merged changeset to the working copy - // and keep the rearrangement from chosen to merged changeset in MT/work + std::set + working_uncommon_ancestors, + chosen_uncommon_ancestors; - merge_change_sets(old_to_chosen, - old_to_working, - chosen_to_merged, - working_to_merged, - merger, app); - // dump_change_set("chosen to merged", chosen_to_merged); - // dump_change_set("working to merged", working_to_merged); + app.db.get_uncommon_ancestors(r_old_id, r_chosen_id, + working_uncommon_ancestors, + chosen_uncommon_ancestors); - update = working_to_merged; - remaining = chosen_to_merged; - } + // Now merge the working roster with the chosen target. + + roster_merge_result result; + roster_merge(working_roster, working_mm, working_uncommon_ancestors, + chosen_roster, chosen_mm, chosen_uncommon_ancestors, + result); + + roster_t & merged_roster = result.roster; + + content_merge_working_copy_adaptor wca(app); + resolve_merge_conflicts (r_old_id, r_chosen_id, + working_roster, chosen_roster, + working_mm, chosen_mm, + result, wca, app); + + I(result.is_clean()); + merged_roster.check_sane(); + + // we have the following + // + // old --> working + // | | + // V V + // chosen --> merged + // + // - old is the revision specified in MT/revision + // - working is based on old and includes the working copy's changes + // - chosen is the revision we're updating to and will end up in MT/revision + // - merged is the merge of working and chosen + // + // we apply the working to merged cset to the working copy + // and write the cset from chosen to merged changeset in MT/work - bookkeeping_path tmp_root = bookkeeping_root / "tmp"; - if (directory_exists(tmp_root)) - delete_dir_recursive(tmp_root); + cset update, remaining; + make_cset (working_roster, merged_roster, update); + make_cset (chosen_roster, merged_roster, remaining); - mkdir_p(tmp_root); - apply_rearrangement_to_filesystem(update.rearrangement, tmp_root); - write_file_targets(update, merger, app); + // { + // data t1, t2, t3; + // write_cset(update, t1); + // write_cset(remaining, t2); + // write_manifest_of_roster(merged_roster, t3); + // P(F("updating working copy with [[[\n%s\n]]]\n") % t1); + // P(F("leaving residual work [[[\n%s\n]]]\n") % t2); + // P(F("merged roster [[[\n%s\n]]]\n") % t3); + // } - if (directory_exists(tmp_root)) - delete_dir_recursive(tmp_root); + update_source fsource(wca.temporary_store, app); + editable_working_tree ewt(app, fsource); + update.apply_to(ewt); // small race condition here... // nb: we write out r_chosen, not r_new, because the revision-on-disk @@ -2939,12 +2961,11 @@ } P(F("updated to base revision %s\n") % r_chosen_id); - put_path_rearrangement(remaining.rearrangement); + put_work_cset(remaining); update_any_attrs(app); maybe_update_inodeprints(app); } -*/ CMD(merge, N_("tree"), "", N_("merge unmerged heads of branch"), OPT_BRANCH_NAME % OPT_DATE % OPT_AUTHOR % OPT_LCA) ======================================================================== --- database.cc fabe0520e53048014dabe606d40e462e846e6993 +++ database.cc b140c52f36629793e95c6578f6ccbef219d3e517 @@ -2475,6 +2475,13 @@ roster_id = hexenc(res[0][0]); } +void +database::get_roster(revision_id const & rev_id, + roster_t & roster) +{ + marking_map mm; + get_roster(rev_id, roster, mm); +} void database::get_roster(revision_id const & rev_id, ======================================================================== --- database.hh 0661609a63093c8c9543a316d0d3f630ee15f762 +++ database.hh af2a926539d042f61e97180730ad348ad2181bcd @@ -418,6 +418,9 @@ void get_branches(std::vector & names); // roster and node_id stuff + + void get_roster(revision_id const & rid, + roster_t & roster); void get_roster(revision_id const & rid, roster_t & roster, ======================================================================== --- diff_patch.cc ebe5306eb73976216ea8d862ed6ae2ce0986af88 +++ diff_patch.cc 3255afd06951ac28adafe13c7cea7437c6fd3db7 @@ -456,18 +456,16 @@ } -merge_provider::merge_provider(app_state & app, - roster_t const & anc_ros, - roster_t const & left_ros, - roster_t const & right_ros) - : app(app), anc_ros(anc_ros), left_ros(left_ros), right_ros(right_ros) -{} +/////////////////////////////////////////////////////////////////////////// +// content_merge_database_adaptor +/////////////////////////////////////////////////////////////////////////// -void merge_provider::record_merge(file_id const & left_ident, - file_id const & right_ident, - file_id const & merged_ident, - file_data const & left_data, - file_data const & merged_data) +void +content_merge_database_adaptor::record_merge(file_id const & left_ident, + file_id const & right_ident, + file_id const & merged_ident, + file_data const & left_data, + file_data const & merged_data) { L(F("recording successful merge of %s <-> %s into %s\n") % left_ident % right_ident % merged_ident); @@ -481,24 +479,85 @@ guard.commit(); } -void merge_provider::get_version(file_path const & path, - file_id const & ident, - file_data & dat) +void +content_merge_database_adaptor::get_version(file_path const & path, + file_id const & ident, + file_data & dat) { app.db.get_file_version(ident, dat); } -std::string merge_provider::get_file_encoding(file_path const & path, - roster_t const & ros) + +/////////////////////////////////////////////////////////////////////////// +// content_merge_working_copy_adaptor +/////////////////////////////////////////////////////////////////////////// + +void +content_merge_working_copy_adaptor::record_merge(file_id const & left_id, + file_id const & right_id, + file_id const & merged_id, + file_data const & left_data, + file_data const & merged_data) +{ + L(F("temporarily recording merge of %s <-> %s into %s\n") + % left_id % right_id % merged_id); + I(temporary_store.find(merged_id) == temporary_store.end()); + temporary_store.insert(make_pair(merged_id, merged_data)); +} + +void +content_merge_working_copy_adaptor::get_version(file_path const & path, + file_id const & ident, + file_data & dat) { + if (app.db.file_version_exists(ident)) + app.db.get_file_version(ident, dat); + else + { + data tmp; + file_id fid; + require_path_is_file(path, + F("file '%s' does not exist in working copy") % path, + F("'%s' in working copy is a directory, not a file") % path); + read_localized_data(path, tmp, app.lua); + calculate_ident(tmp, fid); + N(fid == ident, + F("file %s in working copy has id %s, wanted %s") + % path % fid % ident); + dat = tmp; + } +} + + +/////////////////////////////////////////////////////////////////////////// +// content_merger +/////////////////////////////////////////////////////////////////////////// + +content_merger::content_merger(app_state & app, + roster_t const & anc_ros, + roster_t const & left_ros, + roster_t const & right_ros, + content_merge_adaptor & adaptor) + : app(app), + anc_ros(anc_ros), + left_ros(left_ros), + right_ros(right_ros), + adaptor(adaptor) +{} + +std::string +content_merger::get_file_encoding(file_path const & path, + roster_t const & ros) +{ attr_value v; if (get_attribute_from_roster(ros, path, encoding_attribute, v)) return v(); return default_encoding; } -bool merge_provider::attribute_manual_merge(file_path const & path, - roster_t const & ros) +bool +content_merger::attribute_manual_merge(file_path const & path, + roster_t const & ros) { attr_value v; if (get_attribute_from_roster(ros, path, manual_merge_attribute, v) @@ -507,14 +566,15 @@ return false; // default: enable auto merge } -bool merge_provider::try_to_merge_files(file_path const & anc_path, - file_path const & left_path, - file_path const & right_path, - file_path const & merged_path, - file_id const & ancestor_id, - file_id const & left_id, - file_id const & right_id, - file_id & merged_id) +bool +content_merger::try_to_merge_files(file_path const & anc_path, + file_path const & left_path, + file_path const & right_path, + file_path const & merged_path, + file_id const & ancestor_id, + file_id const & left_id, + file_id const & right_id, + file_id & merged_id) { // This version of try_to_merge_files should only be called when there is a // real merge3 to perform. @@ -535,9 +595,9 @@ file_data left_data, right_data, ancestor_data; data left_unpacked, ancestor_unpacked, right_unpacked, merged_unpacked; - this->get_version(left_path, left_id, left_data); - this->get_version(anc_path, ancestor_id, ancestor_data); - this->get_version(right_path, right_id, right_data); + adaptor.get_version(left_path, left_id, left_data); + adaptor.get_version(anc_path, ancestor_id, ancestor_data); + adaptor.get_version(right_path, right_id, right_data); left_unpacked = left_data.inner(); ancestor_unpacked = ancestor_data.inner(); @@ -576,8 +636,8 @@ merge_data = file_data(tmp); merged_id = merged_fid; - record_merge(left_id, right_id, merged_fid, - left_data, merge_data); + adaptor.record_merge(left_id, right_id, merged_fid, + left_data, merge_data); return true; } @@ -606,20 +666,21 @@ merge_data = file_data(merged_unpacked); merged_id = merged_fid; - record_merge(left_id, right_id, merged_fid, - left_data, merge_data); + adaptor.record_merge(left_id, right_id, merged_fid, + left_data, merge_data); return true; } return false; } -bool merge_provider::try_to_merge_files(file_path const & left_path, - file_path const & right_path, - file_path const & merged_path, - file_id const & left_id, - file_id const & right_id, - file_id & merged_id) +bool +content_merger::try_to_merge_files(file_path const & left_path, + file_path const & right_path, + file_path const & merged_path, + file_id const & left_id, + file_id const & right_id, + file_id & merged_id) { I(!null_id(left_id)); I(!null_id(right_id)); @@ -637,8 +698,8 @@ return true; } - this->get_version(left_path, left_id, left_data); - this->get_version(right_path, right_id, right_data); + adaptor.get_version(left_path, left_id, left_data); + adaptor.get_version(right_path, right_id, right_data); left_unpacked = left_data.inner(); right_unpacked = right_data.inner(); @@ -663,8 +724,8 @@ merge_data = file_data(merged_unpacked); merged_id = merged_fid; - record_merge(left_id, right_id, merged_fid, - left_data, merge_data); + adaptor.record_merge(left_id, right_id, merged_fid, + left_data, merge_data); return true; } @@ -672,50 +733,6 @@ } -// during the "update" command, the only real differences from merging -// are that we take our right versions from the filesystem, not the db, -// and we only record the merges in a transient, in-memory table. - -update_merge_provider::update_merge_provider(app_state & app, - roster_t const & anc_ros, - roster_t const & left_ros, - roster_t const & right_ros) - : merge_provider(app, anc_ros, left_ros, right_ros) {} - -void update_merge_provider::record_merge(file_id const & left_id, - file_id const & right_id, - file_id const & merged_id, - file_data const & left_data, - file_data const & merged_data) -{ - L(F("temporarily recording merge of %s <-> %s into %s\n") - % left_id % right_id % merged_id); - I(temporary_store.find(merged_id) == temporary_store.end()); - temporary_store.insert(make_pair(merged_id, merged_data)); -} - -void update_merge_provider::get_version(file_path const & path, - file_id const & ident, - file_data & dat) -{ - if (app.db.file_version_exists(ident)) - app.db.get_file_version(ident, dat); - else - { - data tmp; - file_id fid; - require_path_is_file(path, - F("file '%s' does not exist in working copy") % path, - F("'%s' in working copy is a directory, not a file") % path); - read_localized_data(path, tmp, app.lua); - calculate_ident(tmp, fid); - N(fid == ident, - F("file %s in working copy has id %s, wanted %s") - % path % fid % ident); - dat = tmp; - } -} - // the remaining part of this file just handles printing out various // diff formats for the case where someone wants to *read* a diff // rather than apply it. ======================================================================== --- diff_patch.hh e4e4bf98edbab213f7deab03c90ef306976ebcc8 +++ diff_patch.hh b94e63ba12f09789dcc416ea61ec621774d25557 @@ -35,74 +35,95 @@ std::vector const & right, std::vector & merged); -struct merge_provider +struct +content_merge_adaptor { - app_state & app; - roster_t const & anc_ros; - roster_t const & left_ros; - roster_t const & right_ros; - merge_provider(app_state & app, - roster_t const & anc_ros, - roster_t const & left_ros, - roster_t const & right_ros); - - // merge3 on a file (line by line) - virtual bool try_to_merge_files(file_path const & anc_path, - file_path const & left_path, - file_path const & right_path, - file_path const & merged_path, - file_id const & ancestor_id, - file_id const & left_id, - file_id const & right, - file_id & merged_id); - - // merge2 on a file (line by line) - virtual bool try_to_merge_files(file_path const & left_path, - file_path const & right_path, - file_path const & merged_path, - file_id const & left_id, - file_id const & right_id, - file_id & merged); - virtual void record_merge(file_id const & left_ident, file_id const & right_ident, file_id const & merged_ident, file_data const & left_data, - file_data const & merged_data); + file_data const & merged_data) = 0; virtual void get_version(file_path const & path, file_id const & ident, - file_data & dat); + file_data & dat) = 0; - virtual std::string get_file_encoding(file_path const & path, - roster_t const & ros); + virtual ~content_merge_adaptor() {} +}; - virtual bool attribute_manual_merge(file_path const & path, - roster_t const & ros); +struct +content_merge_database_adaptor + : public content_merge_adaptor +{ + app_state & app; + content_merge_database_adaptor (app_state & app) : app(app) {} + void record_merge(file_id const & left_ident, + file_id const & right_ident, + file_id const & merged_ident, + file_data const & left_data, + file_data const & merged_data); - virtual ~merge_provider() {} + void get_version(file_path const & path, + file_id const & ident, + file_data & dat); }; -struct update_merge_provider : public merge_provider +struct +content_merge_working_copy_adaptor + : public content_merge_adaptor { std::map temporary_store; - update_merge_provider(app_state & app, - roster_t const & anc_ros, - roster_t const & left_ros, - roster_t const & right_ros); + app_state & app; + content_merge_working_copy_adaptor (app_state & app) : app(app) {} + void record_merge(file_id const & left_ident, + file_id const & right_ident, + file_id const & merged_ident, + file_data const & left_data, + file_data const & merged_data); + + void get_version(file_path const & path, + file_id const & ident, + file_data & dat); +}; - virtual void record_merge(file_id const & left_ident, - file_id const & right_ident, - file_id const & merged_ident, - file_data const & left_data, - file_data const & merged_data); +struct content_merger +{ + app_state & app; + roster_t const & anc_ros; + roster_t const & left_ros; + roster_t const & right_ros; - virtual void get_version(file_path const & path, - file_id const & ident, - file_data & dat); + content_merge_adaptor & adaptor; - virtual ~update_merge_provider() {} + content_merger(app_state & app, + roster_t const & anc_ros, + roster_t const & left_ros, + roster_t const & right_ros, + content_merge_adaptor & adaptor); + + // merge3 on a file (line by line) + bool try_to_merge_files(file_path const & anc_path, + file_path const & left_path, + file_path const & right_path, + file_path const & merged_path, + file_id const & ancestor_id, + file_id const & left_id, + file_id const & right, + file_id & merged_id); + + // merge2 on a file (line by line) + bool try_to_merge_files(file_path const & left_path, + file_path const & right_path, + file_path const & merged_path, + file_id const & left_id, + file_id const & right_id, + file_id & merged); + + std::string get_file_encoding(file_path const & path, + roster_t const & ros); + + bool attribute_manual_merge(file_path const & path, + roster_t const & ros); }; - #endif // __DIFF_PATCH_HH__ ======================================================================== --- merge.cc dc75799326eea15af4fc9cfd65dc3a82f38613d6 +++ merge.cc 506f5389c61de30a65a1d0b4a47a85bf0dd767a5 @@ -1,4 +1,5 @@ // copyright (C) 2005 nathaniel smith +// copyright (C) 2005 graydon hoare // all rights reserved. // licensed to the public under the terms of the GNU GPL (>= 2) // see the file COPYING for details @@ -31,8 +32,7 @@ else { rout = shared_ptr(new roster_t()); - marking_map mm; - app.db.get_roster(rid, *rout, mm); + app.db.get_roster(rid, *rout); safe_insert(rmap, make_pair(rid, rout)); } } @@ -51,33 +51,21 @@ } void -interactive_merge_and_store(revision_id const & left_rid, - revision_id const & right_rid, - revision_id & merged_rid, - app_state & app) +resolve_merge_conflicts(revision_id const & left_rid, + revision_id const & right_rid, + roster_t const & left_roster, + roster_t const & right_roster, + marking_map const & left_marking_map, + marking_map const & right_marking_map, + roster_merge_result & result, + content_merge_adaptor & adaptor, + app_state & app) { - roster_t left_roster, right_roster; - marking_map left_marking_map, right_marking_map; - std::set left_uncommon_ancestors, right_uncommon_ancestors; - - app.db.get_roster(left_rid, left_roster, left_marking_map); - app.db.get_roster(right_rid, right_roster, right_marking_map); - app.db.get_uncommon_ancestors(left_rid, right_rid, - left_uncommon_ancestors, right_uncommon_ancestors); - - roster_merge_result result; - - roster_merge(left_roster, left_marking_map, left_uncommon_ancestors, - right_roster, right_marking_map, right_uncommon_ancestors, - result); - - roster_t & merged_roster = result.roster; - // FIXME_ROSTERS: we only have code (below) to invoke the // line-merger on content conflicts. Other classes of conflict will // cause an invariant to trip below. Probably just a bunch of lua // hooks for remaining conflict types will be ok. - + if (!result.is_clean()) { L(F("unclean mark-merge: %d name conflicts, %d content conflicts, %d attr conflicts\n") @@ -155,21 +143,55 @@ file_id merged_id; - merge_provider mp(app, *roster_for_file_lca, left_roster, right_roster); - if (mp.try_to_merge_files(anc_path, left_path, right_path, right_path, + content_merger cm(app, *roster_for_file_lca, + left_roster, right_roster, + adaptor); + + if (cm.try_to_merge_files(anc_path, left_path, right_path, right_path, anc_id, left_id, right_id, merged_id)) { L(F("resolved content conflict %d / %d\n") % (i+1) % result.file_content_conflicts.size()); + file_t f = downcast_to_file_t(result.roster.get_node(conflict.nid)); + f->content = merged_id; } else residual_conflicts.push_back(conflict); } result.file_content_conflicts = residual_conflicts; - } + } } +} +void +interactive_merge_and_store(revision_id const & left_rid, + revision_id const & right_rid, + revision_id & merged_rid, + app_state & app) +{ + roster_t left_roster, right_roster; + marking_map left_marking_map, right_marking_map; + std::set left_uncommon_ancestors, right_uncommon_ancestors; + app.db.get_roster(left_rid, left_roster, left_marking_map); + app.db.get_roster(right_rid, right_roster, right_marking_map); + app.db.get_uncommon_ancestors(left_rid, right_rid, + left_uncommon_ancestors, right_uncommon_ancestors); + + roster_merge_result result; + + roster_merge(left_roster, left_marking_map, left_uncommon_ancestors, + right_roster, right_marking_map, right_uncommon_ancestors, + result); + + roster_t & merged_roster = result.roster; + + content_merge_database_adaptor dba(app); + resolve_merge_conflicts (left_rid, right_rid, + left_roster, right_roster, + left_marking_map, right_marking_map, + result, dba, app); + // write new files into the db I(result.is_clean()); ======================================================================== --- merge.hh cce807c1a1f9d187eec49d576481e1a7a4647b15 +++ merge.hh cd2797de2edf9cc40867385e4a8167be98e7ddfa @@ -1,7 +1,8 @@ #ifndef __MERGE_HH__ #define __MERGE_HH__ // copyright (C) 2005 nathaniel smith +// copyright (C) 2005 graydon hoare // all rights reserved. // licensed to the public under the terms of the GNU GPL (>= 2) // see the file COPYING for details @@ -12,9 +13,27 @@ #include "vocab.hh" #include "roster.hh" +// Destructively alter a roster_merge_result to attempt to remove any +// conflicts in it. Takes a content_merge_adaptor to pass on to the content +// merger; used from both the merge-to-database code (below) and the +// merge-to-working-copy "update" code in commands.cc. + +struct roster_merge_result; + +void +resolve_merge_conflicts(revision_id const & left_rid, + revision_id const & right_rid, + roster_t const & left_roster, + roster_t const & right_roster, + marking_map const & left_marking_map, + marking_map const & right_marking_map, + roster_merge_result & result, + content_merge_adaptor & adaptor, + app_state & app); + // traditional resolve-all-conflicts-as-you-go style merging with 3-way merge // for file texts -// aborts if merge fails +// throws if merge fails // writes out resulting revision to the db, along with author and date certs // (but _not_ branch or changelog certs) // this version can only be used to merge revisions that are in the db, and ======================================================================== --- paths.hh 6ae0e9e03232b0919c62bf80814e8d10c7b15883 +++ paths.hh ea4ee8e37e6c0d93fddd24395ddc82b44e0c9cbb @@ -200,6 +200,11 @@ bookkeeping_path operator /(std::string const & to_append) const; // exposed for the use of walk_tree static bool is_bookkeeping_path(std::string const & path); + bool operator ==(const bookkeeping_path & other) const + { return data == other.data; } + + bool operator <(const bookkeeping_path & other) const + { return data < other.data; } }; extern bookkeeping_path const bookkeeping_root; ======================================================================== --- roster.cc a253a4d916c62272b592bfd0d008abd30dd28f7b +++ roster.cc 47b7f10128451b5bccc5611487f90e804454a479 @@ -821,7 +821,7 @@ else { I(!null_name(n->name) && !null_node(n->parent)); - !null_id(downcast_to_file_t(n)->content); + I(!null_id(downcast_to_file_t(n)->content)); } for (full_attr_map_t::const_iterator i = n->attrs.begin(); i != n->attrs.end(); ++i) I(i->second.first || i->second.second().empty()); ======================================================================== --- work.cc 963086ebd20315f2ccfabdf79a69813f77199701 +++ work.cc 017ae079b4de961c8e2dd41b180b79e04b8c5f6e @@ -15,6 +15,7 @@ #include "file_io.hh" #include "platform.hh" #include "sanity.hh" +#include "safe_map.hh" #include "transforms.hh" #include "vocab.hh" #include "work.hh" @@ -773,6 +774,13 @@ { } +void +move_path_if_not_already_present(any_path const & old_path, + any_path const & new_path, + app_state & app) +{ +} + static inline bookkeeping_path path_for_nid(node_id nid) { @@ -819,6 +827,7 @@ source.get_file_content(content, dat); // FIXME_ROSTERS: what about charset conversion etc.? write_data(pth, dat.inner()); + safe_insert(written_content, make_pair(pth, content)); return nid; } @@ -827,6 +836,29 @@ { bookkeeping_path src_pth = path_for_nid(nid); file_path dst_pth(dst); + switch (get_path_status(src_pth)) + { + case path::nonexistent: + I(false); + break; + case path::file: + if (file_exists(dst_pth)) + { + std::map::const_iterator i + = written_content.find(src_pth); + I(i != written_content.end()); + file_id dst_id; + ident_existing_file(dst_pth, dst_id, app.lua); + if (i->second == dst_id) + return; + } + break; + case path::directory: + if (directory_exists(dst_pth)) + return; + break; + } + // This will complain if the move is actually impossible move_path(src_pth, dst_pth); } ======================================================================== --- work.hh f19c2815fabb8816a73e5907d168e94b271cb639 +++ work.hh 56b2b9be3b0cb7fcabee21e948788a57658af3eb @@ -208,12 +208,13 @@ struct file_content_source { virtual void get_file_content(file_id const & fid, - file_data const & dat) const = 0; + file_data & dat) const = 0; virtual ~file_content_source() {}; }; struct editable_working_tree : public editable_tree { + std::map written_content; editable_working_tree(app_state & app, file_content_source const & source); virtual node_id detach_node(split_path const & src);