# # patch "agraph.ml" # from [a6e6dbc0c5569090a3941d0b323a55e7423edd26] # to [112863631f39bca61cfc4c43ff3d69062491c14e] # # patch "database.ml" # from [0a59a5a2fdccf2a72da9bb14afc92d709932e6dc] # to [4d43388028b28466b150b96a39af19c6fd5201d7] # # patch "database.mli" # from [8839002cdbfcdd75ee27a6acbbcd54fcd3ff3554] # to [16ab65994da34c28262d423624e154ff3652cae5] # # patch "query.ml" # from [e1f5dd585b0849a5241df459d0796856636f7b9d] # to [0774985cb60091adbc66de0375801b89eb3dd7af] # # patch "subprocess.ml" # from [f01fde6d68f1d9bce01092b4c57bae73ce1ccb83] # to [b1d4d0f67fe01838a729f90c585d62ce12dbb0d4] # # patch "subprocess.mli" # from [b509614ed2a16c4c0f88a299484cc5bb3a9adeae] # to [1601b1332bc1818d5066273d9a18ee64ff665e60] # # patch "view.ml" # from [c5852ee52bcf4a2e65f0da517cacca19cb6de336] # to [6ee2e4a63701af0bdeed8b77a544f91ef9b96c93] # # patch "viz_types.ml" # from [81a02391285185725a67343f32a8f08fb8c94989] # to [ac47415114692a19bdf32928c5d78f2402e1fa47] # # patch "viz_types.mli" # from [53b8aa3cf55af126fe3ce9b6e68ee43c139e2875] # to [292b0505def1f086f2705c3bcc7d10f780bca15a] # ======================================================================== --- agraph.ml a6e6dbc0c5569090a3941d0b323a55e7423edd26 +++ agraph.ml 112863631f39bca61cfc4c43ff3d69062491c14e @@ -214,10 +214,10 @@ Printf.kprintf (fun s -> done_cb (`LAYOUT_ERROR s)) fmt in try status#push "Running dot ..." ; - Subprocess.spawn_inout + Subprocess.spawn ~encoding:`NONE ~cmd - ~input:(dot_format graph.layout_params graph.agraph) + ~input:(Some (dot_format graph.layout_params graph.agraph)) ~reap_callback:status#pop (fun ~exceptions ~stdout ~stderr status -> graph.dot_subproc <- None ; ======================================================================== --- database.ml 0a59a5a2fdccf2a72da9bb14afc92d709932e6dc +++ database.ml 4d43388028b28466b150b96a39af19c6fd5201d7 @@ -38,9 +38,10 @@ (fun acc row -> monot_decode row.(0) :: acc) []) -let view_name = "ids_of_branch" +let view_name_domain = "ids_of_branch" +let view_name_limit = "ids_of_branch_with_date_limit" -let bool_of_sql_string s = s <> "0" +let sql_of_bool b = if b then `INT 1 else `INT 0 let id_set_add_if t v s = if t && v <> "" then IdSet.add v s else s @@ -79,30 +80,35 @@ g.neighbour_nodes)) } -let process_ancestry_row_child g = function - | [| "" ; child ; _ |] -> - if NodeMap.mem child g.nodes - then g - else - let new_node = { id = child ; kind = REGULAR ; family = [] } in - let nodes = NodeMap.add child new_node g.nodes in - { g with nodes = nodes } +let process_ancestry_row_simple g = function + | [| "" ; child |] when not (NodeMap.mem child g.nodes) -> + let new_node = { id = child ; kind = REGULAR ; family = [] } in + let nodes = NodeMap.add child new_node g.nodes in + { g with nodes = nodes } - | [| parent ; child ; parent_in_query |] -> - let parent_kind = - if bool_of_sql_string parent_in_query - then REGULAR - else NEIGHBOUR_IN in + | [| parent ; child |] -> process_ancestry_row g - parent parent_kind + parent REGULAR child REGULAR + | _ -> g -let process_ancestry_row_parent g = function - | [| parent ; child |] -> +let process_ancestry_row_neigh_out g = function + | [| parent ; child ; "0" |] -> + process_ancestry_row g + parent REGULAR + child NEIGHBOUR_OUT + | [| parent ; child ; _ |] -> + { g with ancestry = EdgeMap.add (parent, child) SAME_BRANCH g.ancestry } + | _ -> g + +let process_ancestry_row_neigh_in g = function + | [| parent ; child ; "0" |] -> process_ancestry_row g - parent REGULAR - child NEIGHBOUR_OUT + parent NEIGHBOUR_IN + child REGULAR + | [| parent ; child ; _ |] -> + { g with ancestry = EdgeMap.add (parent, child) SAME_BRANCH g.ancestry } | _ -> g @@ -164,32 +170,44 @@ init stmt -let fetch_agraph_with_view db query = +let fetch_agraph_with_view db (query, query_limit) = + let view_name_limit = + if query_limit <> QUERY_NO_LIMIT + then view_name_limit + else view_name_domain in + let agraph = Viz_types.empty_agraph in (* grab all node ids and edges we're interested in *) let agraph = - Sqlite3.fetch_f db process_ancestry_row_child agraph - "SELECT parent, child, parent IN %s \ - FROM revision_ancestry WHERE child IN %s" view_name view_name in + Sqlite3.fetch_f db process_ancestry_row_simple agraph + "SELECT parent, child FROM revision_ancestry \ + WHERE parent IN %s AND child IN %s" view_name_limit view_name_limit in + (* also grab neighbor nodes *) let agraph = - Sqlite3.fetch_f db process_ancestry_row_parent agraph - "SELECT parent, child \ - FROM revision_ancestry WHERE parent IN %s AND child NOT IN %s" view_name view_name in + Sqlite3.fetch_f db process_ancestry_row_neigh_out agraph + "SELECT parent, child, child IN %s FROM revision_ancestry + WHERE parent IN %s AND child NOT IN %s" + view_name_domain view_name_limit view_name_limit in + let agraph = + Sqlite3.fetch_f db process_ancestry_row_neigh_in agraph + "SELECT parent, child, parent IN %s FROM revision_ancestry + WHERE child IN %s AND parent != '' AND parent NOT IN %s" + view_name_domain view_name_limit view_name_limit in (* look at changelogs to decide what nodes are 'uninteresting' (ie merge or disapproval nodes) *) let agraph = Sqlite3.fetch_f db process_changelog_row agraph "SELECT id, value FROM revision_certs WHERE name = 'changelog' AND id IN %s" - view_name in + view_name_limit in (* determine the branching edges *) let agraph = begin match query with - | BRANCH _ -> + | QUERY_BRANCHES [ _ ] -> (* we already have the branching edges *) agraph | _ -> @@ -198,52 +216,90 @@ process_branching_edge_row agraph "SELECT parent, child \ FROM revision_ancestry AS A \ - WHERE A.child IN %s AND A.parent NOTNULL AND \ + WHERE A.child IN %s AND A.parent != '' AND \ NOT EXISTS \ (SELECT P.id FROM revision_certs AS C, revision_certs AS P \ WHERE C.id = A.child AND P.id = A.parent \ AND C.name = 'branch' AND P.name = 'branch' \ AND C.value = P.value)" - view_name + view_name_limit end in (* reconnect disconnected components *) let agraph = - if query = ALL + if query = QUERY_ALL then agraph else Components.reconnect (fetch_children db) agraph in agraph +let encode_quote s = + let enc = monot_encode s in + let l = String.length enc in + let o = String.make (l + 2) '\'' in + String.blit enc 0 o 1 l ; + o + let fetch_with_view query db f = + let (query_domain, query_limit) = query in + + let register_date_p () = + match query_limit with + | QUERY_BETWEEN (d1, d2) -> + Sqlite3.create_fun_1 db "date_p" + (fun arg -> + let d = monot_decode (Sqlite3.value_text arg) in + sql_of_bool (d1 <= d && d <= d2)) + | _ -> () in + + let view_query_domain = + match query_domain with + | QUERY_ALL -> Printf.sprintf + "CREATE TEMP VIEW %s AS \ + SELECT DISTINCT id FROM revision_certs + WHERE name = 'branch'" view_name_domain + | QUERY_BRANCHES q -> Printf.sprintf + "CREATE TEMP VIEW %s AS \ + SELECT DISTINCT id FROM revision_certs \ + WHERE name = 'branch' AND value IN (%s)" + view_name_domain + (String.concat ", " + (List.map encode_quote q)) in + + let view_query_date_limit () = + Printf.sprintf + "CREATE TEMP VIEW %s AS \ + SELECT DISTINCT id FROM revision_certs \ + WHERE name = 'date' AND id IN %s AND date_p(value)" + view_name_limit view_name_domain in + Viz_misc.bracket ~before:(fun () -> (* We fetch the ids matching the query (ie those on certain branches) *) (* and store them in a view. *) - Sqlite3.exec db - begin - match query with - | ALL -> Printf.sprintf - "CREATE TEMP VIEW %s AS \ - SELECT DISTINCT id FROM revision_certs" view_name - | BRANCH branch -> Printf.sprintf - "CREATE TEMP VIEW %s AS \ - SELECT DISTINCT id FROM revision_certs \ - WHERE name = 'branch' AND value = '%s'" - view_name (monot_encode branch) - | COLLECTION branch -> Printf.sprintf - "CREATE TEMP VIEW %s AS \ - SELECT DISTINCT id FROM revision_certs \ - WHERE name = 'branch' AND unbase64(value) LIKE '%s%%'" - view_name (sql_escape branch) - end) + Sqlite3.exec db view_query_domain ; + if query_limit <> QUERY_NO_LIMIT + then begin + register_date_p () ; + Sqlite3.exec db (view_query_date_limit ()) + end) ~action:(fun () -> f db query) - ~after:(fun () -> Sqlite3.exec_f db "DROP VIEW %s" view_name) + ~after:(fun () -> + if query_limit <> QUERY_NO_LIMIT + then begin + Sqlite3.delete_function db "date_p" ; + Sqlite3.exec_f db "DROP VIEW %s" view_name_limit + end ; + Sqlite3.exec_f db "DROP VIEW %s" view_name_domain) () let fetch_agraph query db = - fetch_with_view query db fetch_agraph_with_view + try fetch_with_view query db fetch_agraph_with_view + with exn -> + Printf.eprintf "fetch_agraph exn: %s\n%!" + (Printexc.to_string exn) ; + raise exn let decode_and_parse_revision s = Revision_parser.revision_set @@ -318,14 +374,14 @@ -let spawn_monotone monotone_exe db_fname cmd status cb = +let spawn_monotone monotone_exe db_fname cmd input status cb = let cmd = monotone_exe :: "--db" :: db_fname :: cmd in if Viz_misc.debug "exec" then Printf.eprintf "### exec: Running '%s'\n%!" (String.concat " " cmd) ; try status#push "Running monotone ..." ; - Subprocess.spawn_out - ~encoding:`NONE ~cmd + Subprocess.spawn + ~encoding:`NONE ~cmd ~input ~reap_callback:status#pop (fun ~exceptions ~stdout ~stderr status -> if status = 0 @@ -447,9 +503,69 @@ monotone_exe db.filename [ "--revision" ; old_id ; "--revision" ; new_id ; "diff" ] - status cb) + None status cb) + +let encode_automate_stdio selectors = + let b = Buffer.create 512 in + List.iter + (fun s -> + Printf.bprintf b "l6:select" ; + Printf.bprintf b "%d:%se\n" (String.length s) s) + selectors ; + let r = Buffer.contents b in + Viz_misc.log "stdio" "stdio input: %S" r ; + r + +let decode_automate_stdio s = + let rec loop acc cmd_buf i = + if i >= String.length s + then List.rev acc + else begin + let c1 = String.index_from s i ':' in + let number = int_of_string (string_slice ~s:i ~e:c1 s) in + let code = int_of_char s.[c1 + 1] - int_of_char '0' in + let c2 = String.index_from s (c1 + 1) ':' in + let last = s.[c2 + 1] in + let c3 = String.index_from s (c2 + 1) ':' in + let c4 = String.index_from s (c3 + 1) ':' in + let len = int_of_string (string_slice ~s:(c3 + 1) ~e:c4 s) in + Buffer.add_substring cmd_buf s (c4 + 1) len ; + + match code with + | 0 when last = 'l' -> + let output = Buffer.contents cmd_buf in + Buffer.clear cmd_buf ; + loop ((number, output) :: acc) cmd_buf (c4 + 1 + len) + | _ when last = 'l' -> + let msg = Buffer.contents cmd_buf in + Viz_misc.log "stdio" "got a stdio error (code=%d): %S" code msg ; + failwith msg + | _ -> + Buffer.add_substring cmd_buf s (c4 + 1) len ; + loop acc cmd_buf (c4 + 1 + len) + end in + loop [] (Buffer.create 1024) 0 + + +let collect_ids stdio_output = + Viz_misc.list_uniq + (List.fold_left + (fun acc (_, output) -> + (string_split '\n' output) @ acc) + [] + stdio_output) + let run_monotone_select db monotone_exe status cb selectors = spawn_monotone + monotone_exe db.filename [ "automate" ; "stdio" ] + (Some (encode_automate_stdio selectors)) + status + (function + | `OUTPUT s -> + let ids = + try `IDS (collect_ids (decode_automate_stdio s)) + with Failure msg -> `SUB_PROC_ERROR msg in + cb ids + | `SUB_PROC_ERROR _ as r -> + cb r) - monotone_exe db.filename [ "automate" ; "select" ; selectors ] - status cb ======================================================================== --- database.mli 8839002cdbfcdd75ee27a6acbbcd54fcd3ff3554 +++ database.mli 16ab65994da34c28262d423624e154ff3652cae5 @@ -32,5 +32,5 @@ val run_monotone_select : t -> string -> Status.reporter -> + ([>`SUB_PROC_ERROR of string | `IDS of string list] -> unit) -> + string list -> Subprocess.t - ([>`SUB_PROC_ERROR of string | `OUTPUT of string] -> unit) -> - string -> Subprocess.t ======================================================================== --- query.ml e1f5dd585b0849a5241df459d0796856636f7b9d +++ query.ml 0774985cb60091adbc66de0375801b89eb3dd7af @@ -13,14 +13,19 @@ module Selector = struct - let make_selector g sel = + let date_limit sel = function + | QUERY_NO_LIMIT -> sel + | QUERY_BETWEEN (d1, d2) -> + Printf.sprintf "l:%s/e:%s/%s" d1 d2 sel + + let make_selectors g sel = match Agraph.get_query g with - | ALL -> - sel - | BRANCH b -> - Printf.sprintf "b:%s/%s" b sel - | COLLECTION b -> - Printf.sprintf "b:%s*/%s" b sel + | QUERY_ALL, lim -> + [ date_limit sel lim ] + | QUERY_BRANCHES br, lim -> + List.map + (fun b -> date_limit (Printf.sprintf "b:%s/%s" b sel) lim) + br let running_select = ref None @@ -40,12 +45,8 @@ status (fun r -> running_select := None ; - match r with - | `OUTPUT l -> - cont (`IDS (Viz_misc.string_split '\n' l)) - | `SUB_PROC_ERROR _ as err -> - cont err) - (make_selector g sel) in + cont r) + (make_selectors g sel) in running_select := Some id end ======================================================================== --- subprocess.ml f01fde6d68f1d9bce01092b4c57bae73ce1ccb83 +++ subprocess.ml b1d4d0f67fe01838a729f90c585d62ce12dbb0d4 @@ -133,7 +133,7 @@ mutable status : int ; } -let spawn encoding input_opt cmd reap_callback done_callback = +let spawn ~encoding ~cmd ~input:input_opt ~reap_callback done_callback = let has_input = input_opt <> None in let spawn_flags = [ `PIPE_STDOUT ; `PIPE_STDERR ; @@ -210,14 +210,6 @@ stderr:string -> int -> unit -(* spawn a process and grab its stdout and stderr *) -let spawn_out ~encoding ~cmd ~reap_callback done_callback = - spawn encoding None cmd reap_callback done_callback - -(* spawn a process, feed it a string and grab its stdout and stderr *) -let spawn_inout ~encoding ~cmd ~input ~reap_callback done_callback = - spawn encoding (Some input) cmd reap_callback done_callback - let abort sub_data = if not sub_data.aborted then begin sub_data.aborted <- true ; ======================================================================== --- subprocess.mli b509614ed2a16c4c0f88a299484cc5bb3a9adeae +++ subprocess.mli 1601b1332bc1818d5066273d9a18ee64ff665e60 @@ -8,16 +8,10 @@ stderr:string -> int -> unit -val spawn_out : +val spawn : encoding:encoding -> - cmd:string list -> - reap_callback:(unit -> unit) -> - callback -> t - -val spawn_inout : - encoding:encoding -> cmd:string list -> - input:string -> + input:string option -> reap_callback:(unit -> unit) -> callback -> t ======================================================================== --- view.ml c5852ee52bcf4a2e65f0da517cacca19cb6de336 +++ view.ml 6ee2e4a63701af0bdeed8b77a544f91ef9b96c93 @@ -406,12 +406,23 @@ let query = match combo#active with | -1 -> raise Exit - | 0 -> ALL + | 0 -> QUERY_ALL | i -> let b = s.branches.(i - 1) in - if s.sub#active then COLLECTION b else BRANCH b in + if s.sub#active + then begin + let c = ref [ b ] in + let is_pref x = string_is_prefix (b ^ ".") x in + for j = 0 to Array.length s.branches - 1 do + let br = s.branches.(j) in + if is_pref br then c := br :: !c + done ; + QUERY_BRANCHES !c + end + else + QUERY_BRANCHES [ b ] in Signal.emit s.select_signal - { query = query ; preselect = id } + { query = (query, QUERY_NO_LIMIT) ; preselect = id } with Exit -> () let with_inactive_combo ({ combo = (combo, _) } as s) f = ======================================================================== --- viz_types.ml 81a02391285185725a67343f32a8f08fb8c94989 +++ viz_types.ml ac47415114692a19bdf32928c5d78f2402e1fa47 @@ -5,10 +5,14 @@ module StringMap = Map.Make(String) -type query = - | ALL - | BRANCH of string - | COLLECTION of string +type query_domain = + | QUERY_ALL + | QUERY_BRANCHES of string list +type date = string +type query_limit = + | QUERY_NO_LIMIT + | QUERY_BETWEEN of date * date +type query = query_domain * query_limit module NodeMap = StringMap module EdgeMap = Map.Make (struct type t = string * string let compare = Pervasives.compare end) ======================================================================== --- viz_types.mli 53b8aa3cf55af126fe3ce9b6e68ee43c139e2875 +++ viz_types.mli 292b0505def1f086f2705c3bcc7d10f780bca15a @@ -7,10 +7,14 @@ module StringMap : Map.S with type key = string -type query = - | ALL - | BRANCH of string - | COLLECTION of string +type query_domain = + | QUERY_ALL + | QUERY_BRANCHES of string list +type date = string +type query_limit = + | QUERY_NO_LIMIT + | QUERY_BETWEEN of date * date +type query = query_domain * query_limit module NodeMap : Map.S with type key = string module EdgeMap : Map.S with type key = string * string