# # # patch "cmd.hh" # from [dd969e8f9644c72b50fb94c56cd5655d095f160d] # to [dd0d008ed178f160ecb909907d9fa287ca22e9c1] # # patch "cmd_othervcs.cc" # from [ef15ac782752f29f76cd018f8fbfb8f79d80fa19] # to [011da227e2639852ae0bb23e263c91031a18d026] # # patch "commands.cc" # from [ffdd5b2702579552a41423973c63802067d5a770] # to [aa02dcafea0bae5e50764d2e9d0b59fbdc9f5d14] # ============================================================ --- cmd.hh dd969e8f9644c72b50fb94c56cd5655d095f160d +++ cmd.hh dd0d008ed178f160ecb909907d9fa287ca22e9c1 @@ -287,6 +287,7 @@ CMD_FWD_DECL(rcs); CMD_FWD_DECL(network); CMD_FWD_DECL(packet_io); CMD_FWD_DECL(rcs); +CMD_FWD_DECL(git); CMD_FWD_DECL(review); CMD_FWD_DECL(tree); CMD_FWD_DECL(variables); ============================================================ --- cmd_othervcs.cc ef15ac782752f29f76cd018f8fbfb8f79d80fa19 +++ cmd_othervcs.cc 011da227e2639852ae0bb23e263c91031a18d026 @@ -1,4 +1,5 @@ // Copyright (C) 2002 Graydon Hoare +// Copyright (C) 2009 Derek Scherger // // This program is made available under the GNU GPL version 2.0 or // greater. See the accompanying file COPYING for details. @@ -10,12 +11,27 @@ #include "base.hh" #include "cmd.hh" #include "app_state.hh" +#include "cert.hh" #include "database.hh" #include "project.hh" +#include "parallel_iter.hh" #include "rcs_import.hh" +#include "revision.hh" +#include "roster.hh" +#include "simplestring_xform.hh" #include "keys.hh" #include "key_store.hh" +#include +#include +#include + +using std::cerr; +using std::cout; +using std::map; +using std::ostringstream; +using std::set; +using std::string; using std::vector; CMD(rcs_import, "rcs_import", "", CMD_REF(debug), N_("RCSFILE..."), @@ -61,7 +77,415 @@ CMD(cvs_import, "cvs_import", "", CMD_RE import_cvs_repo(project, keys, cvsroot, app.opts.branchname); } +namespace +{ + string quote_path(file_path const & path) + { + string raw = path.as_internal(); + string quoted; + quoted.reserve(raw.size() + 8); + + quoted += "\""; + + for (string::const_iterator i = raw.begin(); i != raw.end(); ++i) + { + if (*i == '"') + quoted += "\\"; + quoted += *i; + } + + quoted += "\""; + return quoted; + } + + // FIXME: perhaps add lua hooks for fixing branch names, author strings, etc. + + struct file_delete + { + file_path path; + file_delete(file_path path) : + path(path) {} + }; + + struct file_rename + { + file_path old_path; + file_path new_path; + file_rename(file_path old_path, file_path new_path) : + old_path(old_path), new_path(new_path) {} + }; + + struct file_add + { + file_path path; + file_id content; + string mode; + file_add(file_path path, file_id content, string mode) : + path(path), content(content), mode(mode) {} + }; + + attr_key exe_attr("mtn:execute"); + + void + get_changes(roster_t const & left, roster_t const & right, + vector & deletions, + vector & renames, + vector & additions) + { + + parallel::iter i(left.all_nodes(), right.all_nodes()); + while (i.next()) + { + MM(i); + switch (i.state()) + { + case parallel::invalid: + I(false); + + case parallel::in_left: + // deleted + if (is_file_t(i.left_data())) + { + file_path path; + left.get_name(i.left_key(), path); + deletions.push_back(file_delete(path)); + } + break; + + case parallel::in_right: + // added + if (is_file_t(i.right_data())) + { + file_t file = downcast_to_file_t(i.right_data()); + + full_attr_map_t::const_iterator + exe = file->attrs.find(exe_attr); + + string mode = "100644"; + if (exe != file->attrs.end() && + exe->second.first && // live attr + exe->second.second() == "true") + mode = "100755"; + + file_path path; + right.get_name(i.right_key(), path); + additions.push_back(file_add(path, file->content, mode)); + } + break; + + case parallel::in_both: + // moved/renamed/patched/attribute changes + if (is_file_t(i.left_data())) + { + file_t left_file = downcast_to_file_t(i.left_data()); + file_t right_file = downcast_to_file_t(i.right_data()); + + full_attr_map_t::const_iterator + left_attr = left_file->attrs.find(exe_attr); + full_attr_map_t::const_iterator + right_attr = right_file->attrs.find(exe_attr); + + string left_mode = "100644"; + string right_mode = "100644"; + + if (left_attr != left_file->attrs.end() && + left_attr->second.first && // live attr + left_attr->second.second() == "true") + left_mode = "100755"; + + if (right_attr != right_file->attrs.end() && + right_attr->second.first && // live attr + right_attr->second.second() == "true") + right_mode = "100755"; + + file_path left_path, right_path; + left.get_name(i.left_key(), left_path); + right.get_name(i.right_key(), right_path); + + if (left_path != right_path) + renames.push_back(file_rename(left_path, right_path)); + + // git handles content changes as additions + if (left_file->content != right_file->content || + left_mode != right_mode) + additions.push_back(file_add(right_path, + right_file->content, + right_mode)); + } + break; + } + } + } + +}; + +CMD(git_export, "git_export", "", CMD_REF(git), N_(""), + N_("Produces a git fast-export data stream on stdout"), + N_(""), + options::opts::none) +{ + database db(app); + + if (args.size() != 0) + throw usage(execid); + + set revision_set; + db.get_revision_ids(revision_set); + + vector revisions; + toposort(db, revision_set, revisions); + + size_t revnum = 0; + size_t revmax = revisions.size(); + + map marked_revs; + map marked_files; + + size_t mark_id = 1; + + // this is done to ensure mktime below produces UTC times + // according to timegm(3) this is the portable way to do it + setenv("TZ", "UTC", 1); + tzset(); + + for (vector::const_iterator r = revisions.begin(); r != revisions.end(); ++r) + { + revnum++; + + typedef vector< revision > cert_vector; + typedef cert_vector::const_iterator cert_iterator; + + cert_vector authors; + cert_vector branches; + cert_vector changelogs; + cert_vector comments; + cert_vector dates; + cert_vector tags; + + db.get_revision_certs(*r, author_cert_name, authors); + db.get_revision_certs(*r, branch_cert_name, branches); + db.get_revision_certs(*r, changelog_cert_name, changelogs); + db.get_revision_certs(*r, comment_cert_name, comments); + db.get_revision_certs(*r, date_cert_name, dates); + db.get_revision_certs(*r, tag_cert_name, tags); + + string author_name = "unknown"; + string author_email = ""; + time_t author_date = 0; + + cert_iterator author = authors.begin(); + + if (author != authors.end()) + author_name = author->inner().value(); + + size_t lt = author_name.find('<'); + size_t gt = author_name.find('>'); + size_t at = author_name.find('@'); + + // FIXME: parsing of author/email could be better + + if (lt != string::npos && gt != string::npos && lt < gt) + { + author_email = author_name.substr(lt, gt-lt+1); + author_name = trim_ws(author_name.substr(0, lt)) + " "; + // FIXME: ensure remainder of cert value after > is empty + } + else if (lt == string::npos && gt == string::npos && at != string::npos) + { + author_email = "<" + trim_ws(author_name) + ">"; + author_name = ""; + } + + cert_iterator date = dates.begin(); + + if (date != dates.end()) + { + // FIXME: do something better here (would nvm.dates help?) + struct tm tm; + string datestr = date->inner().value(); + strptime(datestr.c_str(), "%Y-%m-%dT%H:%M:%S", &tm); + author_date = mktime(&tm); + } + + // default to master branch if no branch certs exist + string branchname = "master"; + + if (!branches.empty()) + branchname = branches.begin()->inner().value(); + + ostringstream message; + set messages; + + // process comment certs with changelog certs + + changelogs.insert(changelogs.end(), + comments.begin(), comments.end()); + + for (cert_iterator changelog = changelogs.begin(); + changelog != changelogs.end(); ++changelog) + { + string value = changelog->inner().value(); + if (messages.find(value) == messages.end()) + { + messages.insert(value); + message << value; + if (value[value.size()-1] != '\n') + message << "\n"; + } + } + + revision_t revision; + db.get_revision(*r, revision); + + edge_map::const_iterator edge = revision.edges.begin(); + + revision_id parent1, parent2; + + if (revision.edges.size() == 1) + { + parent1 = edge_old_revision(edge); + } + else if (revision.edges.size() == 2) + { + parent1 = edge_old_revision(edge); + ++edge; + parent2 = edge_old_revision(edge); + } + else + I(false); + + // we apparently only need/want the changes from the first parent. + // including the changes from the second parent seems to cause failures + // due to repeated renames. verification of git merge nodes against the + // monotone source seems to show that they are correct. presumably this + // is somehow because of the 'from' and 'merge' lines in exported commits + // below. + + roster_t old_roster, new_roster; + db.get_roster(parent1, old_roster); + db.get_roster(*r, new_roster); + + vector deletions; + vector renames; + vector additions; + + typedef vector::const_iterator delete_iterator; + typedef vector::const_iterator rename_iterator; + typedef vector::const_iterator add_iterator; + + get_changes(old_roster, new_roster, deletions, renames, additions); + + // emit file data blobs for modified and added files + + for (add_iterator i = additions.begin(); i != additions.end(); ++i) + { + if (marked_files.find(i->content) == marked_files.end()) + { + // only mark and emit a blob the first time it is encountered + file_data data; + db.get_file_version(i->content, data); + marked_files[i->content] = mark_id++; + cout << "blob\n" + << "mark :" << marked_files[i->content] << "\n" + << "data " << data.inner()().size() << "\n" + << data.inner()() << "\n"; + } + } + + // FIXME: optionally include these in the commit message + + message << "\n"; + + if (!null_id(parent1)) + message << "Monotone-Parent: " << parent1 << "\n"; + + if (!null_id(parent2)) + message << "Monotone-Parent: " << parent2 << "\n"; + + message << "Monotone-Revision: " << *r << "\n"; + + for ( ; author != authors.end(); ++author) + message << "Monotone-Author: " << author->inner().value() << "\n"; + + for ( ; date != dates.end(); ++date) + message << "Monotone-Date: " << date->inner().value() << "\n"; + + for (cert_iterator branch = branches.begin() ; branch != branches.end(); ++branch) + message << "Monotone-Branch: " << branch->inner().value() << "\n"; + + for (cert_iterator tag = tags.begin(); tag != tags.end(); ++tag) + message << "Monotone-Tag: " << tag->inner().value() << "\n"; + + string data = message.str(); + + marked_revs[*r] = mark_id++; + + cout << "commit refs/heads/" << branchname << "\n" + << "mark :" << marked_revs[*r] << "\n" + << "committer " << author_name << author_email << " " << author_date << " +0000\n" + << "data " << data.size() << "\n" << data << "\n"; + + if (!null_id(parent1)) + cout << "from :" << marked_revs[parent1] << "\n"; + + if (!null_id(parent2)) + cout << "merge :" << marked_revs[parent2] << "\n"; + + for (delete_iterator i = deletions.begin(); i != deletions.end(); ++i) + cout << "D " << quote_path(i->path) << "\n"; + + // FIXME: handle rename ordering issues + for (rename_iterator i = renames.begin(); i != renames.end(); ++i) + cout << "R " + << quote_path(i->old_path) << " " + << quote_path(i->new_path) << "\n"; + + for (add_iterator i = additions.begin(); i != additions.end(); ++i) + cout << "M " << i->mode << " :" + << marked_files[i->content] << " " + << quote_path(i->path) << "\n"; + + // create additional branch refs + if (!branches.empty()) + { + cert_iterator branch = branches.begin(); + branch++; + for ( ; branch != branches.end(); ++branch) + cout << "reset refs/heads/" << branch->inner().value() << "\n" + << "from :" << marked_revs[*r] << "\n"; + } + + // FIXME: posibly create refs/mtn/revid + + // create tag refs + for (cert_iterator tag = tags.begin(); tag != tags.end(); ++tag) + cout << "reset refs/tags/" << tag->inner().value() << "\n" + << "from :" << marked_revs[*r] << "\n"; + + // report progress to stderr + cerr << "progress revision " << *r + << " (" << revnum << "/" << revmax << ")\n"; + + // report progress to the export file which will be reported during import + cout << "progress revision " << *r + << " (" << revnum << "/" << revmax << ")\n" + << "#############################################################\n"; + + // since this creates a fast-import data stream one option for users + // that encounter problems, is to save the data to a text file, split + // it into smaller files as required -- see split(1), and edit the + // offending commands in the file. this technique could also be used + // to fix up various author names, etc. once the basic information has + // been exported from monotone. + + } + + // FIXME: add an option to write out the revision marks to a file + // then use of git fast-import --export-marks will give a corresponding file + // of git revision ids +} + // Local Variables: // mode: C++ // fill-column: 76 ============================================================ --- commands.cc ffdd5b2702579552a41423973c63802067d5a770 +++ commands.cc aa02dcafea0bae5e50764d2e9d0b59fbdc9f5d14 @@ -80,6 +80,9 @@ CMD_GROUP(rcs, "rcs", "", CMD_REF(__root CMD_GROUP(rcs, "rcs", "", CMD_REF(__root__), N_("Commands for interaction with RCS and CVS"), ""); +CMD_GROUP(git, "git", "", CMD_REF(__root__), + N_("Commands for interaction with GIT"), + ""); CMD_GROUP(review, "review", "", CMD_REF(__root__), N_("Commands to review revisions"), "");