monotone-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Monotone-devel] user-defined external mergers...


From: Richard Levitte - VMS Whacker
Subject: [Monotone-devel] user-defined external mergers...
Date: Fri, 17 Jun 2005 02:41:20 +0200 (CEST)

Hi,

while experimenting with rebuilding a repository with a different
branch name, I thought that there could be some way to specify a
arbitrary merging commands (one for 2-way and one for 3-way merge) to
be used by monotone.

I haven't done any thorough tests yet, but have produced some code
that should be usable as a start (I'm gonna test it soon).

The current idea is that the user can specify commands in the
environment variables MONOTONE_MERGE2 and MONOTONE_MERGE3.  They can
contain the following printf-like items:

        %L      is replaced with left_path
        %A      is replaced with and_path (only for MONOTONE_MERGE3)
        %R      is replaced with right_path
        %M      is replaced with merged_path
        %l      is replaced with lfile
        %a      is replaced with afile (only for MONOTONE_MERGE3)
        %r      is replaced with rfile
        %o      is replaced with outfile

I have tested that the replacement of those elements work as expected,
and that the string is properly split into an argv table.  I have not
checked that execution works as expected.

Comments?

Cheers,
Richard

P.S At least, I'm getting quite a bit better at hacking Lua :-)

-----
Please consider sponsoring my work on free software.
See http://www.free.lp.se/sponsoring.html for details.

-- 
Richard Levitte                         address@hidden
                                        http://richard.levitte.org/

"When I became a man I put away childish things, including
 the fear of childishness and the desire to be very grown up."
                                                -- C.S. Lewis
# 
# patch "lua.cc"
#  from [c5a01c71aef633d6ad5ad78650e4e0601295b349]
#    to [3eb5a2685c16c5e4af1c26da98ec66c0833fde19]
# 
# patch "std_hooks.lua"
#  from [9b1a27e30129929aa366223e51f1c11ec918fc93]
#    to [9fffd02ab6d987a9f2c0edc50987d38cf0441d71]
# 
--- lua.cc
+++ lua.cc
@@ -166,6 +166,137 @@
     lua_pushboolean(L, guess_binary(std::string(path, lua_strlen(L, -1))));
     return 1;
   }
+
+  int
+  add_to_string(char **str, size_t *str_size, size_t *str_len,
+               const char *add, size_t add_len)
+  {
+    while ((*str_len) + add_len + 1 > (*str_size))
+      {
+       char *new_str = (char *)realloc(*str, (*str_size) + 1024);
+       if (new_str == NULL)
+         return 0;
+       *str = new_str;
+       (*str_size) += 1024;
+      }
+    strncpy((*str) + (*str_len), add, add_len);
+    (*str_len) += add_len;
+    (*str)[*str_len] = '\0';
+    return 1;
+  }
+
+  static int
+  monotone_split_command_string_for_lua(lua_State *L)
+  {
+    int n = lua_gettop(L);
+    if (n != 1)
+      return 0;
+    const char *str = lua_tostring(L, -n);
+    char *result = (char *)malloc(1024);
+    size_t result_size = 1024;
+    size_t result_len = 0;
+    int result_any = 0;                // set to 1 when there is a string, even
+                               // an empty one.
+    size_t amount = 0;         // Number of returned strings.
+
+    char state = '\0';         // '\0', '"' or '\''
+    const char *start = str;
+    while(1)
+      {
+       int c = *str;
+
+       // Check to see if we've found the end of an argument.
+       if ((c == '\0' || isspace(c)) && state == '\0')
+         {
+           // If there's more to add to the result string since last
+           // addition, do it.
+           if (start != str)
+             {
+               add_to_string(&result, &result_size, &result_len,
+                             start, str - start);
+               result_any = 1;
+             }
+
+           // If there was a result at all, push it.  If not, we've just
+           // going to walk past a bunch of spaces.
+           if (result_any)
+             {
+               lua_pushlstring(L, result, result_len);
+               amount++;
+               result_len = 0;
+               result_any = 0;
+             }
+
+           // If we've found the end of the command string, it's to to quit.
+           if (c == '\0')
+             break;
+
+           // Walk past all spaces but the last (because str is incremented
+           // once more further down)
+           while(isspace(str[1]))
+             str++;
+
+           // Set the start of the next chunk to be after the last space.
+           start = &str[1];
+         }
+       // Escape, escape, escape.
+       else if (c == '\\')
+         {
+           if (str[1] != '\0')
+             {
+               add_to_string(&result, &result_size, &result_len,
+                             start, str - start);
+               start = ++str;
+             }
+         }
+       // Check if we've found the end of the current string.  We've
+       // already taken care of the case where c and state are '\0'.
+       else if (c == state)
+         {
+           // If there's more to add to the result string since last
+           // addition, do it.
+           if (start != str)
+             {
+               add_to_string(&result, &result_size, &result_len,
+                             start, str - start);
+               result_any = 1;
+             }
+
+           // Set the state to be "outside of any quoted string".
+           state = '\0';
+
+           // Set the start of the next chunk to be after the quote.
+           start = &str[1];
+         }
+       else if (c == '"' || c == '\'')
+         {
+           // If there's more to add to the result string since last
+           // addition, do it.
+           if (start != str)
+             {
+               add_to_string(&result, &result_size, &result_len,
+                             start, str - start);
+               result_any = 1;
+             }
+
+           // Set the state to be "inside a quoted string".
+           state = c;
+
+           // Set the start of the next chunk to be after the quote.
+           start = &str[1];
+         }
+       else if (c == '\0')
+         {
+           // End of string in the middle of a quote is an error.
+           // Respond by removing all the pushed strings and return 0.
+           while(amount)
+             lua_remove(L, amount--);
+           break;
+         }
+       str++;
+      }
+    return amount;
+  }
 }
 
 
@@ -194,6 +325,7 @@
   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_register(st, "split_command_string", 
monotone_split_command_string_for_lua);
 }
 
 lua_hooks::~lua_hooks()
--- std_hooks.lua
+++ std_hooks.lua
@@ -19,8 +19,8 @@
    return ret
 end
 
+         
 
-
 -- attributes are persistent metadata about files (such as execute
 -- bit, ACLs, various special flags) which we want to have set and
 -- re-set any time the files are modified. the attributes themselves
@@ -310,6 +310,42 @@
    end
 end
 
+function merge2_generic_cmd(cmd_format,
+                            left_path, right_path, merged_path,
+                            lfile, rfile, outfile)
+   return 
+   function()
+      local tmp = {split_command_string(cmd_format)}
+      local t = {L=left_path, R=right_path, M=merged_path,
+                 l=lfile, r=rfile, o=outfile}
+      local argv = {}
+      for k, v in tmp do
+         argv[k] = string.gsub(v, "%%(%w)", function (x)
+                                               return t[x]
+                                            end)
+      end
+      return execute(unpack(argv))
+   end
+end
+
+function merge3_generic_cmd(cmd_format,
+                            left_path, anc_path, right_path, merged_path, 
+                            lfile, afile, rfile, outfile)
+   return 
+   function()
+      local tmp = {split_command_string(cmd_format)}
+      local t = {L=left_path, A=anc_path, R=right_path, M=merged_path,
+                 l=lfile, a=afile, r=rfile, o=outfile}
+      local argv = {}
+      for k, v in tmp do
+         argv[k] = string.gsub(v, "%%(%w)", function (x)
+                                               return t[x]
+                                            end)
+      end
+      return execute(unpack(argv))
+   end
+end
+
 function write_to_temporary_file(data)
    tmp, filename = temp_file()
    if (tmp == nil) then 
@@ -343,11 +379,15 @@
    local rfile = tbl.rfile
    local outfile = tbl.outfile 
 
+   local generic_merger = os.getenv("MONOTONE_MERGE2")
    local editor = os.getenv("EDITOR")
    if editor ~= nil then editor = string.lower(editor) else editor = "" end
 
-   
-   if program_exists_in_path("kdiff3") then
+   if generic_merger ~= nil then
+      cmd = merge2_generic_cmd (generic_merger,
+                                left_path, right_path, merged_path,
+                                lfile, rfile, outfile)
+   elseif program_exists_in_path("kdiff3") then
       cmd =   merge2_kdiff3_cmd (left_path, right_path, merged_path, lfile, 
rfile, outfile) 
    elseif program_exists_in_path ("meld") then 
       tbl.meld_exists = true 
@@ -396,7 +436,7 @@
 
       local cmd = get_preferred_merge2_command (tbl) 
 
-      if cmd ~=nil 
+      if cmd ~= nil 
       then 
          io.write (string.format("executing external 2-way merge command\n"))
          cmd ()
@@ -433,10 +473,15 @@
    local rfile = tbl.rfile
    local outfile = tbl.outfile 
 
+   local generic_merger = os.getenv("MONOTONE_MERGE3")
    local editor = os.getenv("EDITOR")
    if editor ~= nil then editor = string.lower(editor) else editor = "" end
 
-   if program_exists_in_path("kdiff3") then
+   if generic_merger ~= nil then
+      cmd = merge3_generic_cmd (generic_merger,
+                                left_path, anc_path, right_path, merged_path,
+                                lfile, afile, rfile, outfile)
+   elseif program_exists_in_path("kdiff3") then
       cmd = merge3_kdiff3_cmd (left_path, anc_path, right_path, merged_path, 
lfile, afile, rfile, outfile) 
    elseif program_exists_in_path ("meld") then 
       tbl.meld_exists = true 
@@ -488,7 +533,7 @@
    if tbl.lfile ~= nil and tbl.rfile ~= nil and tbl.afile ~= nil and 
tbl.outfile ~= nil 
    then 
       local cmd =   get_preferred_merge3_command (tbl) 
-      if cmd ~=nil 
+      if cmd ~= nil
       then 
          io.write (string.format("executing external 3-way merge command\n"))
          cmd ()

reply via email to

[Prev in Thread] Current Thread [Next in Thread]