Fix "mtn diff" output by handling the special cases of missing trailing newline at end of file. This makes the output compatible with GNU diffutils' diff(1) and especially allows it to be correctly applied with GNU patch(1) again, too. The source of the problem is that diff_patch:cc:make_diff() called the generic function simplestring_xform.cc:split_into_lines() for splitting the file data into individual lines, this way removed all(!) newlines at all and hence lost the information about the special case of a missing trailing newline. The least invasive fix is to already preserve the information in simplestring_xform.cc:split_into_lines() by representing the missing newline with the appended text "[\r]\n\\No newline at end of file" on the last line (notice that this is the only case were a line can still contain a newline after splitting). Additionally, diff_patch.cc:walk_hunk_consumer() for unknown reasons produces added before deleted lines (instead of the expection order of deleted before added lines) and hence also had to be fixed in order to produce output 100% compatible to GNU diff(1). Finally, two existing tests related to diff_patch.cc:walk_hunk_consumer() were adjusted and a new test suite was added for checking all four special cases related to the missing trailing newline. The complete Monotone test suite still succeeed with 0 failures after this change. Can be removed with Monotone >= 0.40! Index: diff_patch.cc --- diff_patch.cc e359a4c03c6ce548d6414dce11160fbcb6fcb831 +++ diff_patch.cc daeb9be7d8c10d33781b54debd541027618dbfce @@ -1010,17 +1010,17 @@ void walk_hunk_consumer(vector lines1, lines2; - split_into_lines(data1(), lines1); - split_into_lines(data2(), lines2); + split_into_lines(data1(), lines1, true); + split_into_lines(data2(), lines2, true); vector left_interned; vector right_interned; Index: simplestring_xform.cc --- simplestring_xform.cc 5f2e4fcd288a2ec1fd59feb97ebc777b56597608 +++ simplestring_xform.cc dcaa75eb0ed67ddc119ef62f06c21bd7d13bf5cb @@ -50,9 +50,24 @@ void split_into_lines(string const & in, } void split_into_lines(string const & in, + vector & out, + bool diff_compat) +{ + return split_into_lines(in, constants::default_encoding, out, diff_compat); +} + +void split_into_lines(string const & in, string const & encoding, vector & out) { + return split_into_lines(in, encoding, out, false); +} + +void split_into_lines(string const & in, + string const & encoding, + vector & out, + bool diff_compat) +{ string lc_encoding = lowercase(encoding); out.clear(); @@ -92,8 +107,16 @@ void split_into_lines(string const & in, break; end = in.find_first_of("\r\n", begin); } - if (begin < in.size()) - out.push_back(in.substr(begin, in.size() - begin)); + if (begin < in.size()) { + // special case: last line without trailing newline + string s = in.substr(begin, in.size() - begin); + if (diff_compat) { + // special handling: produce diff(1) compatible output + s += (in.find_first_of("\r") != string::npos ? "\r\n" : "\n"); + s += "\\ No newline at end of file"; + } + out.push_back(s); + } } else { Index: simplestring_xform.hh --- simplestring_xform.hh 0280d49b889bc0b7b2900d5d123cc8ec95a115eb +++ simplestring_xform.hh 564a305c99ca931c93956ac3ef903683dcb15db7 @@ -13,6 +13,15 @@ void split_into_lines(std::string const std::string const & encoding, std::vector & out); +void split_into_lines(std::string const & in, + std::vector & out, + bool diff_compat); + +void split_into_lines(std::string const & in, + std::string const & encoding, + std::vector & out, + bool diff_compat); + void join_lines(std::vector const & in, std::string & out, std::string const & linesep); ----------------------------------------------------------------------------- Fix merging in a special case. Can be removed with Monotone >= 0.40! --- roster_merge.cc d565f9299ca85d77bd1d7f2a29196d7161f0ea08 +++ roster_merge.cc d113e5d648bd98bdc15ba7a53958496ea924bd2b @@ -525,15 +525,35 @@ roster_merge_result::report_duplicate_na left_roster.get_name(left_nid, left_name); right_roster.get_name(right_nid, right_name); - I(left_name == right_name); - shared_ptr left_lca_roster, right_lca_roster; revision_id left_lca_rid, right_lca_rid; adaptor.get_ancestral_roster(left_nid, left_lca_rid, left_lca_roster); adaptor.get_ancestral_roster(right_nid, right_lca_rid, right_lca_roster); - P(F("conflict: duplicate name '%s'") % left_name); + // In most cases, the left_name equals the right_name. However, maybe + // a parent directory got renamed on one side. In that case, the names + // don't match, but it's still the same directory (by node id), to + // which we want to add the same file (by name). + if (left_name == right_name) + { + file_path dir; + path_component basename; + left_name.dirname_basename(dir, basename); + P(F("conflict: duplicate name '%s' for the directory '%s'") % basename % dir); + } + else + { + file_path left_dir, right_dir; + path_component left_basename, right_basename; + left_name.dirname_basename(left_dir, left_basename); + right_name.dirname_basename(right_dir, right_basename); + I(left_basename == right_basename); + P(F("conflict: duplicate name '%s' for the directory\n" + " named '%s' on the left and\n" + " named '%s' on the right.") + % left_basename % left_dir % right_dir); + } node_type left_type = get_type(left_roster, left_nid); node_type right_type = get_type(right_roster, right_nid);