#
# delete_file "tests/t_merge_binary.at"
#
# add_file "tests/t_merge_manual.at"
#
# patch "ChangeLog"
# from [0c7ce88d4c09b80dab45c122aad3aa8bd0ec670c]
# to [eff9a1b084062e5c3330260c8b501e8f5952381f]
#
# patch "diff_patch.cc"
# from [209e60efcca30474071383769840ae18d3d1aeef]
# to [cb8bea5dd5b9e5b9c4eb838dd89bb85200c434aa]
#
# patch "diff_patch.hh"
# from [8045a7d688a9494585eab247815ef9573687039f]
# to [18be28ae5d744c23da230988bd4a48f6556cb4b4]
#
# patch "file_io.cc"
# from [35b0d27bb9a0a1c72b651fd2081a0f9a6d6c58f1]
# to [6125dbcc50e69e63ba8a22664e3e8126ee1115a0]
#
# patch "file_io.hh"
# from [d2865f7695f667b872b858b276edcec5d7cf1605]
# to [80ef83f6cd2479dd5dbcfd13bca81172d90fde4d]
#
# patch "lua.cc"
# from [107b2deb3efdabe34542d967503b2250055b4d23]
# to [c5a01c71aef633d6ad5ad78650e4e0601295b349]
#
# patch "monotone.texi"
# from [e4a42059771b12538e575453621b24cc20ddf2ad]
# to [b1147c864ad611f55aec986899580628fc4ceb16]
#
# patch "std_hooks.lua"
# from [572befdb727cd626e7a8efd27d5a8109bdfebd27]
# to [ed37f9501c86a0a007be67f0dc381e92172af818]
#
# patch "tests/t_merge_manual.at"
# from []
# to [30eeeb2ea878e180248e8969dda05c07cd9df743]
#
# patch "testsuite.at"
# from [4a1a612444d0916a4fcbe71053f66ba1d7aaa18e]
# to [870e8ba2df0bf039b75504971f0b4ba11c1cc859]
#
# patch "work.cc"
# from [b165e29831c433d9866bb5d3e03ceccdefd6be39]
# to [a62b401c81e47925f2911689aa985d652eb624ae]
#
# patch "work.hh"
# from [70d8503b81c8ab4d49481cd740f49fa7e4375498]
# to [b632c7b9bda8e2df8b392b3606d554398e6ddb97]
#
--- ChangeLog
+++ ChangeLog
@@ -1,3 +1,15 @@
+2005-06-09 Riccardo Ghetta
+
+ * diff_patch.cc/hh: honor the new manual_merge attribute
+ * file_io.cc/hh: move here the guess_binary function
+ * lua.cc: let guess_binary available to lua
+ * std_hooks.lua: handle manual_merge as an add-time attribute and
+ initialize by default make it true if the file appears to be binary.
+ Make read_contents_of_file able to read "binary" files.
+ * tests/t_merge_manual.at: tests new behaviour, superceding the
+ old XFAIL t_merge_binary.at test.
+ * monotone.texi: document changes, adding a small section on merging.
+
2005-06-07 Nathaniel Smith
* ChangeLog: Fixup.
--- diff_patch.cc
+++ diff_patch.cc
@@ -21,18 +21,6 @@
using namespace std;
-bool guess_binary(string const & s)
-{
- // these do not occur in ASCII text files
- // FIXME: this heuristic is (a) crap and (b) hardcoded. fix both these.
- if (s.find_first_of('\x00') != string::npos ||
- s.find_first_of("\x01\x02\x03\x04\x05\x06\x0e\x0f"
- "\x10\x11\x12\x13\x14\x15\x16\x17\x18"
- "\x19\x1a\x1c\x1d\x1e\x1f") != string::npos)
- return true;
- return false;
-}
-
//
// a 3-way merge works like this:
//
@@ -510,6 +498,18 @@
return default_encoding;
}
+bool merge_provider::attribute_manual_merge(file_path const & path,
+ manifest_map const & man)
+{
+ std::string mmf;
+ if (get_attribute_from_db(path, manual_merge_attribute, man, mmf, app))
+ {
+ return mmf == std::string("true");
+ }
+ else
+ 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,
@@ -537,45 +537,53 @@
file_data left_data, right_data, ancestor_data;
data left_unpacked, ancestor_unpacked, right_unpacked, merged_unpacked;
- string left_encoding, anc_encoding, right_encoding;
- vector left_lines, ancestor_lines, right_lines, merged_lines;
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);
- left_encoding = this->get_file_encoding(left_path, left_man);
- anc_encoding = this->get_file_encoding(anc_path, anc_man);
- right_encoding = this->get_file_encoding(right_path, right_man);
-
left_unpacked = left_data.inner();
ancestor_unpacked = ancestor_data.inner();
right_unpacked = right_data.inner();
- split_into_lines(left_unpacked(), left_encoding, left_lines);
- split_into_lines(ancestor_unpacked(), anc_encoding, ancestor_lines);
- split_into_lines(right_unpacked(), right_encoding, right_lines);
+ if (!attribute_manual_merge(left_path, left_man) &&
+ !attribute_manual_merge(right_path, right_man))
+ {
+ // both files mergeable by monotone internal algorithm, try to merge
+ // note: the ancestor is not considered for manual merging. Forcing the
+ // user to merge manually just because of an ancestor mistakenly marked
+ // manual seems too harsh
+ string left_encoding, anc_encoding, right_encoding;
+ left_encoding = this->get_file_encoding(left_path, left_man);
+ anc_encoding = this->get_file_encoding(anc_path, anc_man);
+ right_encoding = this->get_file_encoding(right_path, right_man);
+
+ vector left_lines, ancestor_lines, right_lines, merged_lines;
+ split_into_lines(left_unpacked(), left_encoding, left_lines);
+ split_into_lines(ancestor_unpacked(), anc_encoding, ancestor_lines);
+ split_into_lines(right_unpacked(), right_encoding, right_lines);
+
+ if (merge3(ancestor_lines,
+ left_lines,
+ right_lines,
+ merged_lines))
+ {
+ hexenc tmp_id;
+ file_data merge_data;
+ string tmp;
+
+ L(F("internal 3-way merged ok\n"));
+ join_lines(merged_lines, tmp);
+ calculate_ident(data(tmp), tmp_id);
+ file_id merged_fid(tmp_id);
+ merge_data = file_data(tmp);
- if (merge3(ancestor_lines,
- left_lines,
- right_lines,
- merged_lines))
- {
- hexenc tmp_id;
- file_data merge_data;
- string tmp;
-
- L(F("internal 3-way merged ok\n"));
- join_lines(merged_lines, tmp);
- calculate_ident(data(tmp), tmp_id);
- file_id merged_fid(tmp_id);
- merge_data = file_data(tmp);
-
- merged_id = merged_fid;
- record_merge(left_id, right_id, merged_fid,
- left_data, merge_data);
-
- return true;
+ merged_id = merged_fid;
+ record_merge(left_id, right_id, merged_fid,
+ left_data, merge_data);
+
+ return true;
+ }
}
P(F("help required for 3-way merge\n"));
@@ -715,8 +723,18 @@
return default_encoding;
}
+bool update_merge_provider::attribute_manual_merge(file_path const & path,
+ manifest_map const & man)
+{
+ std::string mmf;
+ if (get_attribute_from_working_copy(path, manual_merge_attribute, mmf))
+ return mmf == std::string("true");
+ else if (get_attribute_from_db(path, manual_merge_attribute, man, mmf, app))
+ return mmf == std::string("true");
+ else
+ return false; // default: enable auto merge
+}
-
// 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.
@@ -845,7 +863,7 @@
if (b_len == 0)
ost << " +0,0";
else
- {
+ {
ost << " +" << b_begin+1;
if (b_len > 1)
ost << "," << b_len;
--- diff_patch.hh
+++ diff_patch.hh
@@ -19,7 +19,6 @@
// this file is to contain some stripped down, in-process implementations
// of GNU-diffutils-like things (diff, diff3, maybe patch..)
-bool guess_binary(std::string const & s);
enum diff_type
{
@@ -81,6 +80,9 @@
virtual std::string get_file_encoding(file_path const & path,
manifest_map const & man);
+ virtual bool attribute_manual_merge(file_path const & path,
+ manifest_map const & man);
+
virtual ~merge_provider() {}
};
@@ -105,6 +107,9 @@
virtual std::string get_file_encoding(file_path const & path,
manifest_map const & man);
+ virtual bool attribute_manual_merge(file_path const & path,
+ manifest_map const & man);
+
virtual ~update_merge_provider() {}
};
--- file_io.cc
+++ file_io.cc
@@ -254,6 +254,18 @@
return fs::exists(localized(p));
}
+bool guess_binary(string const & s)
+{
+ // these do not occur in ASCII text files
+ // FIXME: this heuristic is (a) crap and (b) hardcoded. fix both these.
+ if (s.find_first_of('\x00') != string::npos ||
+ s.find_first_of("\x01\x02\x03\x04\x05\x06\x0e\x0f"
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18"
+ "\x19\x1a\x1c\x1d\x1e\x1f") != string::npos)
+ return true;
+ return false;
+}
+
void
delete_file(local_path const & p)
{
--- file_io.hh
+++ file_io.hh
@@ -59,6 +59,9 @@
bool file_exists(local_path const & path);
bool file_exists(file_path const & path);
+// returns true if the string content is binary according to monotone euristic
+bool guess_binary(std::string const & s);
+
void mkdir_p(local_path const & path);
void mkdir_p(file_path const & path);
void make_dir_for(file_path const & p);
--- lua.cc
+++ lua.cc
@@ -157,6 +157,15 @@
lua_pushnumber(L, process_sleep(seconds));
return 1;
}
+
+ static int
+ monotone_guess_binary_for_lua(lua_State *L)
+ {
+ const char *path = lua_tostring(L, -1);
+ N(path, F("guess_binary called with an invalid parameter"));
+ lua_pushboolean(L, guess_binary(std::string(path, lua_strlen(L, -1))));
+ return 1;
+ }
}
@@ -184,6 +193,7 @@
lua_register(st, "wait", monotone_wait_for_lua);
lua_register(st, "kill", monotone_kill_for_lua);
lua_register(st, "sleep", monotone_sleep_for_lua);
+ lua_register(st, "guess_binary", monotone_guess_binary_for_lua);
}
lua_hooks::~lua_hooks()
--- monotone.texi
+++ monotone.texi
@@ -2229,6 +2229,7 @@
* Reserved Certs:: Certificate names with special meanings.
* Naming Conventions:: Choosing appropriate names for keys and branches.
* File Attributes:: Marking files as executable, or other attributes.
+* Merging:: Merging with external tools, handling binary files.
* Migrating and Dumping:: Changing the underlying storage system.
* Importing from CVS:: Building a monotone database from a CVS repository.
@end menu
@@ -2898,7 +2899,48 @@
other people make, you will have to resolve those conflicts, as plain
text, just as with any other text file in your working copy.
address@hidden
address@hidden Merging
address@hidden Merging
+Monotone has two merging modes, controlled by the @code{manual_merge}
+attribute.
+By default all files are merged in automatic mode, unless the
address@hidden attribute for that file is present and
address@hidden
+In automatic mode files are merged without user intervention, using
+monotone internal three-way merging algorithm.
+Only if there are conflicts or an ancestor is not available monotone
+switches to manual mode, essentially escalating the merging to the user.
+When working in manual mode, monotone invokes the merge2 (for two-way
+merging) or merge3 (three-way) hooks to start an user defined external
+merge tool.
+If the tool terminates without writing the merged file, monotone aborts the
+merging, reverting any changes made.
+By redefining the aforementioned hooks the user can not only choose a
+preferred merge tool, but even select different programs for different
+file types. For example, gimp for .png files, OpenOffice.org for
+.doc, and so on.
+Starting with monotone 0.20, the @code{manual_merge} attribute is
+automatically set at add time for all ``binary'' files, i.e. all files
+for wich the @code{binary_file} hook returns true.
+Currently, this means all files with extension gif, jpeg, png, bz2, gz
+and zip, plus files containing at least one of the following
+bytes:
+
address@hidden
address@hidden
+0x00 thru 0x06
+0x0E thru 0x1a
+0x1c thru 0x1f
address@hidden group
address@hidden smallexample
+
+The attribute could also be manually forced or removed using the
+apposite monotone commands.
+Remember that monotone switches to manual merging even if only one of
+the files to be merged has the @code{manual_merge} attribute set.
+
@page
@node Migrating and Dumping
@section Migrating and Dumping
@@ -6008,8 +6050,8 @@
stored in @file{.mt-attrs} for the given @var{filename}. This table of
hook functions is called once for each file during an @dfn{add}.
-By default, there is only one entry in this table, for the @code{execute}
-attribute. Its definition is:
+By default, there are only two entries in this table, for the
address@hidden and @code{manual_merge} attributes. Their definition is:
@smallexample
@group
@@ -6021,9 +6063,50 @@
return nil
end
end
+attr_init_functions["manual_merge"] =
+ function(filename)
+ if (binary_file(filename)) then
+ return "true" -- binary files must merged manually
+ else
+ return nil
+ end
+ end
@end group
@end smallexample
+The @code{binary_file} function is also defined as a lua hook as
+follows:
+
address@hidden
address@hidden
+function binary_file(name)
+ local lowname=string.lower(name)
+ -- some known binaries, return true
+ if (string.find(lowname, "%.gif$")) then return true end
+ if (string.find(lowname, "%.jpe?g$")) then return true end
+ if (string.find(lowname, "%.png$")) then return true end
+ if (string.find(lowname, "%.bz2$")) then return true end
+ if (string.find(lowname, "%.gz$")) then return true end
+ if (string.find(lowname, "%.zip$")) then return true end
+ -- some known text, return false
+ if (string.find(lowname, "%.cc?$")) then return false end
+ if (string.find(lowname, "%.cxx$")) then return false end
+ if (string.find(lowname, "%.hh?$")) then return false end
+ if (string.find(lowname, "%.hxx$")) then return false end
+ if (string.find(lowname, "%.lua$")) then return false end
+ if (string.find(lowname, "%.texi$")) then return false end
+ if (string.find(lowname, "%.sql$")) then return false end
+ -- unknown - read file and use the guess-binary built-in
+ -- monotone function
+ filedata=read_contents_of_file(name)
+ if (filedata ~= nil) then return guess_binary(filedata) end
+ -- if still unknown, treat as binary
+ return true
+end
address@hidden group
address@hidden smallexample
+
+
@end ftable
@node Special Topics
--- std_hooks.lua
+++ std_hooks.lua
@@ -25,7 +25,7 @@
-- bit, ACLs, various special flags) which we want to have set and
-- re-set any time the files are modified. the attributes themselves
-- are stored in a file .mt-attrs, in the working copy (and
--- manifest). each (f,k,v) triple in an atribute file turns into a
+-- manifest). each (f,k,v) triple in an attribute file turns into a
-- call to attr_functions[k](f,v) in lua.
if (attr_init_functions == nil) then
@@ -41,11 +41,19 @@
end
end
+attr_init_functions["manual_merge"] =
+ function(filename)
+ if (binary_file(filename)) then
+ return "true" -- binary files must merged manually
+ else
+ return nil
+ end
+ end
+
if (attr_functions == nil) then
attr_functions = {}
end
-
attr_functions["execute"] =
function(filename, value)
if (value == "true") then
@@ -91,6 +99,32 @@
return false;
end
+-- return true means "binary", false means "text",
+-- nil means "unknown, try to guess"
+function binary_file(name)
+ local lowname=string.lower(name)
+ -- some known binaries, return true
+ if (string.find(lowname, "%.gif$")) then return true end
+ if (string.find(lowname, "%.jpe?g$")) then return true end
+ if (string.find(lowname, "%.png$")) then return true end
+ if (string.find(lowname, "%.bz2$")) then return true end
+ if (string.find(lowname, "%.gz$")) then return true end
+ if (string.find(lowname, "%.zip$")) then return true end
+ -- some known text, return false
+ if (string.find(lowname, "%.cc?$")) then return false end
+ if (string.find(lowname, "%.cxx$")) then return false end
+ if (string.find(lowname, "%.hh?$")) then return false end
+ if (string.find(lowname, "%.hxx$")) then return false end
+ if (string.find(lowname, "%.lua$")) then return false end
+ if (string.find(lowname, "%.texi$")) then return false end
+ if (string.find(lowname, "%.sql$")) then return false end
+ -- unknown - read file and use the guess-binary
+ -- monotone built-in function
+ filedata=read_contents_of_file(name, "rb")
+ if (filedata ~= nil) then return guess_binary(filedata) end
+ -- if still unknown, treat as binary
+ return true
+end
function edit_comment(basetext, user_log_message)
local exe = "vi"
@@ -281,8 +315,8 @@
return filename
end
-function read_contents_of_file(filename)
- tmp = io.open(filename, "r")
+function read_contents_of_file(filename, mode)
+ tmp = io.open(filename, mode)
if (tmp == nil) then
return nil
end
@@ -362,9 +396,9 @@
cmd ()
if tbl.meld_exists
then
- ret = read_contents_of_file (tbl.lfile)
+ ret = read_contents_of_file (tbl.lfile, "r")
else
- ret = read_contents_of_file (tbl.outfile)
+ ret = read_contents_of_file (tbl.outfile, "r")
end
if string.len (ret) == 0
then
@@ -453,9 +487,9 @@
cmd ()
if tbl.meld_exists
then
- ret = read_contents_of_file (tbl.afile)
+ ret = read_contents_of_file (tbl.afile, "r")
else
- ret = read_contents_of_file (tbl.outfile)
+ ret = read_contents_of_file (tbl.outfile, "r")
end
if string.len (ret) == 0
then
--- tests/t_merge_manual.at
+++ tests/t_merge_manual.at
@@ -0,0 +1,308 @@
+AT_SETUP([merge manual file])
+MONOTONE_SETUP
+
+NEED_UNB64
+
+# This was a real merge error. A binary file happily merged by monotone
+# just because contains some strategically placed line feeds
+# now is a test for the new attribute merge_manual and its effect on merging
+
+AT_DATA(parent.bmp.b64, [Qk1mdQAAAAAAADYAAAAoAAAAZAAAAGQAAAABABgAAAAAADB1AADrCgAA6woAAAAAAAAAAAAAQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQApC
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQApC2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsyChsy2xsy2xsy2xsy2xsy2xsy2xsy2xsyGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxsKCgoK
+])
+
+AT_DATA(left.bmp.b64, [Qk1mdQAAAAAAADYAAAAoAAAAZAAAAGQAAAABABgAAAAAADB1AADrCgAA6woAAAAAAAAAAAAAQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9C
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQApC
+QJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQJ9CQApC2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsyChsy2xsy2xsy2xsy2xsy2xsy2xsy2xsypzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3pzq3
+pzq3pzq3pzq3GxsKCgoK
+])
+
+AT_DATA(right.bmp.b64, [Qk1mdQAAAAAAADYAAAAoAAAAZAAAAGQAAAABABgAAAAAADB1AADrCgAA6woAAAAAAAAAAAAAOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrt
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtQApC
+OtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtOtrtQApC2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy2xsy
+2xsy2xsy2xsy2xsy2xsy2xsyChsy2xsy2xsy2xsy2xsy2xsy2xsy2xsyGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvbGxvb
+GxvbGxvbGxvbGxsKCgoK
+])
+
+UNB64(parent.bmp.b64, parent.bmp)
+UNB64(left.bmp.b64, left.bmp)
+UNB64(right.bmp.b64, right.bmp)
+
+# hook forces all files binary
+AT_DATA(binary.lua, [if (attr_init_functions == nil) then attr_init_functions = {} end
+attr_init_functions[["manual_merge"]] = function(filename) return "true" end
+])
+
+# hook forces all files text
+AT_DATA(text.lua, [if (attr_init_functions == nil) then attr_init_functions = {} end
+attr_init_functions[["manual_merge"]] = function(filename) return "false" end
+])
+
+# --- first: auto add as binary
+AT_CHECK(cp -f parent.bmp binary.bmp)
+AT_CHECK(MONOTONE --rcfile=binary.lua add binary.bmp, [], [ignore], [ignore])
+COMMIT(binbranch)
+PARENT_SHA=`BASE_REVISION`
+
+AT_CHECK(MONOTONE attr get binary.bmp manual_merge, [], [ignore], [ignore])
+
+AT_CHECK(cp -f left.bmp binary.bmp)
+COMMIT(binbranch)
+
+REVERT_TO($PARENT_SHA)
+
+AT_CHECK(cp -f right.bmp binary.bmp)
+COMMIT(binbranch)
+
+# file marked binary: merge should fail
+AT_CHECK(MONOTONE --branch=binbranch merge, [1], [ignore], [ignore])
+
+# --- second: auto add as text
+AT_CHECK(cp -f parent.bmp text.bmp)
+AT_CHECK(MONOTONE --rcfile=text.lua add text.bmp, [], [ignore], [ignore])
+COMMIT(textbranch)
+PARENT_SHA=`BASE_REVISION`
+
+AT_CHECK(cp -f left.bmp text.bmp)
+COMMIT(textbranch)
+
+REVERT_TO($PARENT_SHA)
+
+AT_CHECK(cp -f right.bmp text.bmp)
+COMMIT(textbranch)
+
+# file marked text: merge should work!
+AT_CHECK(MONOTONE --branch=textbranch merge, [0], [ignore], [ignore])
+
+# --- third: manually make filename as binary
+AT_CHECK(cp -f parent.bmp forcebin.bmp)
+AT_CHECK(MONOTONE --rcfile=text.lua add forcebin.bmp, [], [ignore], [ignore])
+COMMIT(forcebinbranch)
+PARENT_SHA=`BASE_REVISION`
+
+AT_CHECK(cp -f left.bmp forcebin.bmp)
+COMMIT(forcebinbranch)
+
+REVERT_TO($PARENT_SHA)
+
+AT_CHECK(cp -f right.bmp forcebin.bmp)
+
+# set bin
+AT_CHECK(MONOTONE attr set forcebin.bmp manual_merge true, [], [ignore], [ignore])
+COMMIT(forcebinbranch)
+
+# file marked binary: merge should fail
+AT_CHECK(MONOTONE --branch=forcebinbranch merge, [1], [ignore], [ignore])
+
+# --- fourth: automatically make filename as binary, then force text
+AT_CHECK(cp -f parent.bmp forcetext.bmp)
+AT_CHECK(MONOTONE --rcfile=binary.lua add forcetext.bmp, [], [ignore], [ignore])
+AT_CHECK(MONOTONE attr set forcetext.bmp manual_merge false, [], [ignore], [ignore])
+COMMIT(forcetextbranch)
+PARENT_SHA=`BASE_REVISION`
+
+AT_CHECK(cp -f left.bmp forcetext.bmp)
+COMMIT(forcetextbranch)
+
+REVERT_TO($PARENT_SHA)
+
+AT_CHECK(cp -f right.bmp forcetext.bmp)
+COMMIT(forcetextbranch)
+
+# file marked text: merge should work
+AT_CHECK(MONOTONE --branch=forcetextbranch merge, [], [ignore], [ignore])
+
+AT_CLEANUP
--- testsuite.at
+++ testsuite.at
@@ -650,10 +650,10 @@
m4_include(tests/t_annotate_split_line.at)
m4_include(tests/t_automate_certs.at)
m4_include(tests/t_selector_later_earlier.at)
-m4_include(tests/t_merge_binary.at)
m4_include(tests/t_automate_stdio.at)
m4_include(tests/t_cvsimport_drepper.at)
m4_include(tests/t_update_with_pending_drop.at)
m4_include(tests/t_update_with_pending_add.at)
m4_include(tests/t_update_with_pending_rename.at)
m4_include(tests/t_restricted_commit_with_inodeprints.at)
+m4_include(tests/t_merge_manual.at)
--- work.cc
+++ work.cc
@@ -674,6 +674,8 @@
string const binary_encoding("binary");
string const default_encoding("default");
+string const manual_merge_attribute("manual_merge");
+
static bool find_in_attr_map(attr_map const & attr,
file_path const & file,
std::string const & attr_key,
--- work.hh
+++ work.hh
@@ -165,6 +165,7 @@
attr_map const & options);
extern std::string const encoding_attribute;
+extern std::string const manual_merge_attribute;
bool get_attribute_from_db(file_path const & file,
std::string const & attr_key,