#
#
# add_dir "tests/git_export"
#
# add_dir "tests/git_export_rename_loop"
#
# add_file "tests/git_export/__driver__.lua"
# content [72fd4cc32028c10e452023b2747d33fd1dde992e]
#
# add_file "tests/git_export_rename_loop/__driver__.lua"
# content [a1e970df185b451d158f71d0897845d9befa1b89]
#
# patch "Makefile.am"
# from [cc098fcd7d979e7002ab949e577ce5d6ba022dfa]
# to [cff9b91b5e405dce157c262ae10fbc84400cd99d]
#
# patch "cmd_othervcs.cc"
# from [001f139a078dd171823a09b81f22d65d8ef6159b]
# to [8eab7f856845f8b8271ddbed6b5cfd89304ed2c5]
#
============================================================
--- tests/git_export/__driver__.lua 72fd4cc32028c10e452023b2747d33fd1dde992e
+++ tests/git_export/__driver__.lua 72fd4cc32028c10e452023b2747d33fd1dde992e
@@ -0,0 +1,98 @@
+skip_if(not existsonpath("git"))
+
+mtn_setup()
+
+writefile("author.map", "address@hidden =
\n")
+
+writefile("file1", "file1")
+writefile("file2", "file2")
+
+check(mtn("add", "file1", "file2"), 0, false, false)
+check(mtn("commit", "-m", "add file1 and file2", "-b", "branch1"), 0, false, false)
+r1 = base_revision()
+check(mtn("tag", r1, "tag1"), 0, false, false)
+
+writefile("file1", "file1 has changed")
+check(mtn("commit", "-m", "edit file1", "-b", "branch1"), 0, false, false)
+r2 = base_revision()
+check(mtn("tag", r2, "tag2"), 0, false, false)
+
+check(mtn("update", "-r", r1), 0, false, false)
+
+check(mtn("rm", "file2"), 0, false, false)
+check(mtn("commit", "-m", "remove file2", "-b", "branch2"), 0, false, false)
+r3 = base_revision()
+check(mtn("tag", r3, "tag3"), 0, false, false)
+
+check(mtn("mv", "file1", "file-one"), 0, false, false)
+check(mtn("commit", "-m", "rename file1 to file-one", "-b", "branch2"), 0, false, false)
+r4 = base_revision()
+check(mtn("tag", r4, "tag4"), 0, false, false)
+
+check(mtn("propagate", "branch2", "branch1"), 0, false, false)
+check(mtn("update", "-r", "h:branch1"), 0, false, false)
+r5 = base_revision()
+check(mtn("tag", r5, "tag5"), 0, false, false)
+
+-- export the monotone history and import it into git
+
+mkdir("git.dir")
+check(mtn("git_export", "--authors-file", "author.map"), 0, true, false)
+copy("stdout", "stdin")
+check(indir("git.dir", {"git", "init"}), 0, false, false)
+check(indir("git.dir", {"git", "fast-import"}), 0, false, false, true)
+
+-- check the tags we made on each rev above
+
+check(mtn("co", "-r", "t:tag1", "mtn.dir"), 0, false, false)
+check(indir("git.dir", {"git", "checkout", "tag1"}), 0, false, false)
+check(samefile("mtn.dir/file1", "git.dir/file1"))
+check(samefile("mtn.dir/file2", "git.dir/file2"))
+
+remove("mtn.dir")
+check(mtn("co", "-r", "t:tag2", "mtn.dir"), 0, false, false)
+check(indir("git.dir", {"git", "checkout", "tag2"}), 0, false, false)
+check(samefile("mtn.dir/file1", "git.dir/file1"))
+check(samefile("mtn.dir/file2", "git.dir/file2"))
+
+remove("mtn.dir")
+check(mtn("co", "-r", "t:tag3", "mtn.dir"), 0, false, false)
+check(indir("git.dir", {"git", "checkout", "tag3"}), 0, false, false)
+check(samefile("mtn.dir/file1", "git.dir/file1"))
+check(not exists("mtn.dir/file2"))
+check(not exists("git.dir/file2"))
+
+remove("mtn.dir")
+check(mtn("co", "-r", "t:tag4", "mtn.dir"), 0, false, false)
+check(indir("git.dir", {"git", "checkout", "tag4"}), 0, false, false)
+check(samefile("mtn.dir/file-one", "git.dir/file-one"))
+check(not exists("mtn.dir/file2"))
+check(not exists("git.dir/file2"))
+
+remove("mtn.dir")
+check(mtn("co", "-r", "t:tag5", "mtn.dir"), 0, false, false)
+check(indir("git.dir", {"git", "checkout", "tag5"}), 0, false, false)
+check(samefile("mtn.dir/file-one", "git.dir/file-one"))
+check(not exists("mtn.dir/file2"))
+check(not exists("git.dir/file2"))
+
+-- log both repos (mainly for visual inspection)
+
+check(indir("mtn.dir", mtn("log")), 0, false, false)
+check(indir("git.dir", {"git", "log", "--summary", "--pretty=raw"}), 0, false, false)
+
+-- check branch refs
+
+remove("mtn.dir")
+check(mtn("co", "-r", "h:branch2", "mtn.dir"), 0, false, false)
+check(indir("git.dir", {"git", "checkout", "branch2"}), 0, false, false)
+check(samefile("mtn.dir/file-one", "git.dir/file-one"))
+check(not exists("mtn.dir/file2"))
+check(not exists("git.dir/file2"))
+
+remove("mtn.dir")
+check(mtn("co", "-r", "h:branch1", "mtn.dir"), 0, false, false)
+check(indir("git.dir", {"git", "checkout", "branch1"}), 0, false, false)
+check(samefile("mtn.dir/file-one", "git.dir/file-one"))
+check(not exists("mtn.dir/file2"))
+check(not exists("git.dir/file2"))
============================================================
--- tests/git_export_rename_loop/__driver__.lua a1e970df185b451d158f71d0897845d9befa1b89
+++ tests/git_export_rename_loop/__driver__.lua a1e970df185b451d158f71d0897845d9befa1b89
@@ -0,0 +1,50 @@
+skip_if(not existsonpath("git"))
+
+mtn_setup()
+
+writefile("author.map", "address@hidden = \n")
+
+writefile("file1", "file1")
+writefile("file2", "file2")
+writefile("file3", "file3")
+
+check(mtn("add", "file1", "file2", "file3"), 0, false, false)
+commit()
+r1 = base_revision()
+check(mtn("tag", r1, "tag1"), 0, false, false)
+
+check(mtn("mv", "file1", "tmp"), 0, false, false)
+check(mtn("mv", "file3", "file1"), 0, false, false)
+check(mtn("mv", "file2", "file3"), 0, false, false)
+check(mtn("mv", "tmp", "file2"), 0, false, false)
+commit()
+r2 = base_revision()
+check(mtn("tag", r2, "tag2"), 0, false, false)
+
+-- export the monotone history and import it into git
+
+mkdir("git.dir")
+check(mtn("git_export", "--authors-file", "author.map"), 0, true, false)
+copy("stdout", "stdin")
+check(indir("git.dir", {"git", "init"}), 0, false, false)
+check(indir("git.dir", {"git", "fast-import"}), 0, false, false, true)
+
+-- check the tags we made on each rev above
+
+check(mtn("co", "-r", "t:tag1", "mtn.dir"), 0, false, false)
+check(indir("git.dir", {"git", "checkout", "tag1"}), 0, false, false)
+check(samefile("mtn.dir/file1", "git.dir/file1"))
+check(samefile("mtn.dir/file2", "git.dir/file2"))
+check(samefile("mtn.dir/file3", "git.dir/file3"))
+
+remove("mtn.dir")
+check(mtn("co", "-r", "t:tag2", "mtn.dir"), 0, false, false)
+check(indir("git.dir", {"git", "checkout", "tag2"}), 0, false, false)
+check(samefile("mtn.dir/file1", "git.dir/file1"))
+check(samefile("mtn.dir/file2", "git.dir/file2"))
+check(samefile("mtn.dir/file3", "git.dir/file3"))
+
+-- log both repos (mainly for visual inspection)
+
+check(indir("mtn.dir", mtn("log")), 0, false, false)
+check(indir("git.dir", {"git", "log", "-M", "--summary", "-p", "--pretty=raw"}), 0, false, false)
============================================================
--- Makefile.am cc098fcd7d979e7002ab949e577ce5d6ba022dfa
+++ Makefile.am cff9b91b5e405dce157c262ae10fbc84400cd99d
@@ -303,12 +303,12 @@ UNIT_TEST_SOURCES = \
packet.cc paths.cc refiner.cc restrictions.cc rev_height.cc \
revision.cc roster.cc roster_merge.cc simplestring_xform.cc \
string_queue.cc transforms.cc unit_tests.cc uri.cc vocab.cc \
- xdelta.cc
+ xdelta.cc cmd_othervcs.cc
# these files do not contain unit tests, but are required for unit testing
# and must be recompiled for that purpose
UNIT_TEST_SRC_SUPPORT = \
- roster_delta.cc
+ roster_delta.cc rcs_import.cc rcs_file.cc
# these files do not contain unit tests; they are required for unit
# testing, but can be used "as is" from the main build. (many of
============================================================
--- cmd_othervcs.cc 001f139a078dd171823a09b81f22d65d8ef6159b
+++ cmd_othervcs.cc 8eab7f856845f8b8271ddbed6b5cfd89304ed2c5
@@ -27,12 +27,15 @@
#include
#include
+#include
using std::cout;
using std::map;
+using std::make_pair;
using std::istringstream;
using std::ostringstream;
using std::set;
+using std::stack;
using std::string;
using std::vector;
@@ -125,6 +128,10 @@ namespace
path(path), content(content), mode(mode) {}
};
+ typedef vector::const_iterator delete_iterator;
+ typedef vector::const_iterator rename_iterator;
+ typedef vector::const_iterator add_iterator;
+
attr_key exe_attr("mtn:execute");
void
@@ -218,6 +225,76 @@ namespace
}
}
+ // re-order renames so that they occur in the correct order
+ // i.e. rename a->b + rename b->c will be re-ordered as
+ // rename b->c + rename a->b
+ // this will also insert temporary names to resolve circular
+ // renames and name swaps:
+ // i.e. rename a->b + rename b->a will be re-ordered as
+ // rename a->tmp + rename b->a + rename tmp->b
+ void
+ reorder_renames(vector & renames)
+ {
+ typedef map map_type;
+
+ map_type rename_map;
+
+ for (rename_iterator i = renames.begin(); i != renames.end(); ++i)
+ rename_map.insert(make_pair(i->old_path, i->new_path));
+
+ renames.clear();
+
+ while (!rename_map.empty())
+ {
+ map_type::iterator i = rename_map.begin();
+ I(i != rename_map.end());
+ file_rename base(i->first, i->second);
+ rename_map.erase(i);
+
+ map_type::iterator next = rename_map.find(base.new_path);
+ stack rename_stack;
+
+ // stack renames so their order can be reversed
+ while (next != rename_map.end())
+ {
+ file_rename rename(next->first, next->second);
+ rename_stack.push(rename);
+ rename_map.erase(next);
+ next = rename_map.find(rename.new_path);
+ }
+
+ // break rename loops
+ if (!rename_stack.empty())
+ {
+ file_rename const & top = rename_stack.top();
+ // if there is a loop push another rename onto the stack that
+ // renames the old base to a temporary and adjust the base
+ // rename to account for this
+ if (base.old_path == top.new_path)
+ {
+ // the temporary path introduced here is pretty weak in
+ // terms of random filenames but should suffice for the
+ // already rare situations where any of this is required.
+ string path = top.new_path.as_internal();
+ path += ".tmp.break-rename-loop";
+ file_path tmp = file_path_internal(path);
+ rename_stack.push(file_rename(base.old_path, tmp));
+ base.old_path = tmp;
+ }
+ }
+
+ // insert the stacked renames in reverse order
+ while (!rename_stack.empty())
+ {
+ file_rename rename = rename_stack.top();
+ rename_stack.pop();
+ renames.push_back(rename);
+ }
+
+ renames.push_back(base);
+ }
+ }
+
};
static void
@@ -449,11 +526,9 @@ CMD(git_export, "git_export", "", CMD_RE
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);
- get_changes(old_roster, new_roster, deletions, renames, additions);
+ reorder_renames(renames);
// emit file data blobs for modified and added files
@@ -522,7 +597,6 @@ CMD(git_export, "git_export", "", CMD_RE
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) << " "
@@ -606,6 +680,106 @@ CMD(git_export, "git_export", "", CMD_RE
}
}
+#ifdef BUILD_UNIT_TESTS
+
+#include "unit_tests.hh"
+
+UNIT_TEST(git_rename_reordering, reorder_chained_renames)
+{
+ vector renames;
+ renames.push_back(file_rename(file_path_internal("a"), file_path_internal("b")));
+ renames.push_back(file_rename(file_path_internal("b"), file_path_internal("c")));
+ renames.push_back(file_rename(file_path_internal("c"), file_path_internal("d")));
+
+ // these should be reordered from a->b b->c c->d to c->d b->c a->b
+ reorder_renames(renames);
+ rename_iterator rename = renames.begin();
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("c"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("d"));
+ ++rename;
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("b"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("c"));
+ ++rename;
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("a"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("b"));
+ ++rename;
+ UNIT_TEST_CHECK(rename == renames.end());
+}
+
+UNIT_TEST(git_rename_reordering, reorder_swapped_renames)
+{
+ vector renames;
+ renames.push_back(file_rename(file_path_internal("a"), file_path_internal("b")));
+ renames.push_back(file_rename(file_path_internal("b"), file_path_internal("a")));
+
+ // these should be reordered from a->b b->a to a->tmp b->a tmp->b
+ reorder_renames(renames);
+ rename_iterator rename = renames.begin();
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("a"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("a.tmp.break-rename-loop"));
+ ++rename;
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("b"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("a"));
+ ++rename;
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("a.tmp.break-rename-loop"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("b"));
+ ++rename;
+ UNIT_TEST_CHECK(rename == renames.end());
+}
+
+UNIT_TEST(git_rename_reordering, reorder_rename_loop)
+{
+ vector renames;
+ renames.push_back(file_rename(file_path_internal("a"), file_path_internal("b")));
+ renames.push_back(file_rename(file_path_internal("b"), file_path_internal("c")));
+ renames.push_back(file_rename(file_path_internal("c"), file_path_internal("a")));
+
+ // these should be reordered from a->b b->c c->a to a->tmp c->a b->c a->b tmp->b
+ reorder_renames(renames);
+ rename_iterator rename = renames.begin();
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("a"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("a.tmp.break-rename-loop"));
+ ++rename;
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("c"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("a"));
+ ++rename;
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("b"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("c"));
+ ++rename;
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("a.tmp.break-rename-loop"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("b"));
+ ++rename;
+ UNIT_TEST_CHECK(rename == renames.end());
+}
+
+UNIT_TEST(git_rename_reordering, reorder_reversed_rename_loop)
+{
+ vector renames;
+ renames.push_back(file_rename(file_path_internal("z"), file_path_internal("y")));
+ renames.push_back(file_rename(file_path_internal("y"), file_path_internal("x")));
+ renames.push_back(file_rename(file_path_internal("x"), file_path_internal("z")));
+
+ // assuming that the x->z rename gets pulled from the rename map first
+ // these should be reordered from z->y y->x x->z to x->tmp y->x z->y tmp->z
+ reorder_renames(renames);
+ rename_iterator rename = renames.begin();
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("x"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("x.tmp.break-rename-loop"));
+ ++rename;
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("y"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("x"));
+ ++rename;
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("z"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("y"));
+ ++rename;
+ UNIT_TEST_CHECK(rename->old_path == file_path_internal("x.tmp.break-rename-loop"));
+ UNIT_TEST_CHECK(rename->new_path == file_path_internal("z"));
+ ++rename;
+ UNIT_TEST_CHECK(rename == renames.end());
+}
+
+#endif // BUILD_UNIT_TESTS
+
// Local Variables:
// mode: C++
// fill-column: 76