Below is the file 'git.ml' from this revision. You can also download the file.

open Viz_misc
open Viz_types

type id = string

type commit = {
    tree      : id ;
    parents   : id list ;
    author    : string ;
    committer : string ;
    log       : string ;
  }

type changeset = (string * Revision_types.change list) list

type t = {
    base          : string ;
    git_kind      : [`LINUS|`PASKY] ;
    head          : id ;
    get_commit    : (string -> commit) ;
    get_changeset : (string -> changeset) ;
    tags          : (string * string) list ;
    branches      : (string * string) list ;
  }

let id_of_file ?dir f =
  with_file_in
    input_line
    (match dir with None -> f | Some d -> Filename.concat d f)

let list_map_noexn f l =
  List.rev
    (List.fold_left
       (fun acc v -> try f v :: acc with _ -> acc)
       [] l)

let fetch_dir_of_ids git_dir subdir =
  try
    let dir = Filename.concat git_dir subdir in
    list_map_noexn
      (fun n -> (id_of_file ~dir n, n))
      (Array.to_list (Sys.readdir dir))
  with _ -> []

let fetch_tags git_dir =
  fetch_dir_of_ids git_dir "tags"

let fetch_history base id =
  log "exec" "### exec: Running rev-tree %s'" id ;
  match Gspawn.sync
      ~working_directory:base
      ~flags:[`SEARCH_PATH]
      ["rev-tree"; id] with
  | Gspawn.EXITSTATUS 0, stdout, _ ->
      stdout
  | _, _, stderr ->
      Viz_types.errorf "rev-tree invocation failed: '%s'" stderr


let scan_history data =
  let get_id s =
    match string_split ~max_elem:2 ':' s with
    | id :: _ -> id
    | _ -> raise Not_found in

  let rec proc ag i =
    if i >= String.length data
    then ag
    else begin
      let j = String.index_from data i '\n' in
      let l = String.sub data i (j - i) in
      let ag =
	match string_split ' ' l with
	| _ :: node :: parents ->
	    let id = get_id node in
	    let parents = List.map get_id parents in
	    let node = { id = id ;
			 kind = if List.length parents > 1 then MERGE else REGULAR ;
			 family = List.map (fun i -> i, PARENT) parents } in
	    let edges =
	      List.fold_left
		(fun e p -> EdgeMap.add (p, id) SAME_BRANCH e)
		ag.ancestry parents in
	    { ag with
	      nodes = NodeMap.add id node ag.nodes ;
	      ancestry = edges }
	| _ -> ag in
      proc ag (j+1)
    end in

  proc Viz_types.empty_agraph 0

let fetch_commit_object base id =
  log "exec" "### exec: Running 'cat-file commit %s'" id ;
  match Gspawn.sync
      ~working_directory:base
      ~flags:[`SEARCH_PATH]
      ["cat-file"; "commit"; id] with
  | Gspawn.EXITSTATUS 0, stdout, _ -> stdout
  | _, _, stderr ->
      Viz_types.errorf "cat-file invocation failed: '%s'" stderr

let scan_commit_object co =
  let tree = ref "" in
  let parents = ref [] in
  let author = ref "" in
  let committer = ref "" in
  let log = ref "" in
  begin
    let lines = string_split ~collapse:false '\n' co in
    let p = ref 0 in
    try List.iter (function
      | "" -> raise Exit
      | l ->
	  p := !p + String.length l + 1 ;
	  match   string_split ~max_elem:2 ' ' l with
	  | ["tree"; id] -> tree := id
	  | ["parent"; id] -> parents := id :: !parents
	  | ["author"; v] -> author := v
	  | ["committer"; v] -> committer := v
	  | _ -> ()) lines
    with Exit ->
      log := string_slice ~s:(!p + 1) co
  end ;
  { tree = !tree ;
    parents = List.rev !parents ;
    author = !author ;
    committer = !committer ;
    log = !log }

let get_commit_object base id =
  scan_commit_object (fetch_commit_object base id)




let fetch_changeset base old_id new_id =
  log "exec" "### exec: Running 'diff-tree %s %s'" old_id new_id ;
  let tmp_file = Filename.temp_file "git-viz_" ".diff-tree" in
  match
    Gspawn.sync
      ~working_directory:base
      ~flags:[]
      ["/bin/sh"; "-c";
       Printf.sprintf "diff-tree '%s' '%s' > %s" old_id new_id (Filename.quote tmp_file) ]
  with
  | Gspawn.EXITSTATUS 0, _, _ ->
      let stdout = with_file_in input_channel tmp_file in
      Sys.remove tmp_file ;
      stdout
  | _, _, stderr ->
      Sys.remove tmp_file ;
      Viz_types.errorf "diff-tree invocation failed:\n'%s'" stderr

let scan_change_linus l =
  let b = Scanf.Scanning.from_string l in
  match l.[0] with
  | '+' | '-' ->
      Scanf.bscanf b
	"%c%_o %40[0-9a-f] %n"
	(fun c id s ->
	  if c = '+'
	  then Revision_types.ADD_FILE (string_slice ~s:(s-1) l)
	  else Revision_types.DELETE_FILE (string_slice ~s:(s-1) l))
  | '*' ->
      Scanf.bscanf b
	"*%_o->%_o %40[0-9a-f]->%40[0-9a-f] %n"
	(fun id1 id2 s -> Revision_types.PATCH (string_slice ~s:(s-1) l, id1, id2))
  | _ ->
      failwith "Could not parse changeset"

let scan_change_pasky l =
  let a = Array.of_list (string_split '\t' l) in
  match l.[0] with
  | '+' | '-' ->
      begin
	match l.[0], a.(1) with
	| '+', _      -> Revision_types.ADD_FILE a.(3)
	| '-', "blob" -> Revision_types.DELETE_FILE a.(3)
	| '-', "tree" -> Revision_types.DELETE_DIR a.(3)
	| _ -> failwith ""
      end
  | '*' ->
      Revision_types.PATCH (a.(3), string_slice ~e:40 a.(2), string_slice ~s:(-40) a.(2))
  | _ ->
      failwith "Could not parse changeset"

let get_changes k base id1 id2 =
  let (sep, scan_fun) =
    match k with
    | `LINUS -> '\000', scan_change_linus
    | `PASKY -> '\n',   scan_change_pasky in
  List.fold_left
    (fun acc l -> scan_fun l :: acc)
    []
    (string_split sep (fetch_changeset base id1 id2))

let get_changeset k base get_commit id =
  let c = get_commit id in
  List.map
    (fun id_old ->
      let c_old = get_commit id_old in
      id_old, get_changes k base c_old.tree c.tree)
    c.parents


let kind = `DIRECTORY

let open_db db_name =
  let dl = Filename.concat db_name ".dircache" in
  let ds = Filename.concat db_name ".git" in
  try
    let d, kind =
      if Sys.file_exists ds then ds, `PASKY else
      if Sys.file_exists dl then dl, `LINUS else failwith "unknown" in
    let head = id_of_file ~dir:d "HEAD" in
    let get_commit = Viz_misc.make_cache (get_commit_object db_name) in
    let get_changeset = Viz_misc.make_cache (get_changeset kind db_name get_commit) in
    { base = db_name ;
      git_kind = kind ;
      head = head ;
      get_commit = get_commit ;
      get_changeset = get_changeset ;
      tags = fetch_tags d ;
      branches = fetch_dir_of_ids d "heads"
    }
  with Failure _ | Sys_error _ ->
    Viz_types.errorf "Not a git db: %s" db_name

let close_db _ = ()

let get_filename d = d.base

let fetch_branches d =
  List.sort compare
    (List.map snd d.branches)

let fetch_ancestry_graph d q =
  let head =
    match q with
    | BRANCH b ->
	begin
	  try list_rassoc b d.branches
	  with Not_found -> d.head
	end
    | _ -> d.head in
  scan_history (fetch_history d.base head)

let fetch_revision d id =
  { revision_id  = id ;
    manifest_id  = (d.get_commit id).tree ;
    revision_set = d.get_changeset id ;
    certs = []
  }

let fetch_certs_and_revision d id =
  let fake_cert = { c_id = id ;
		    c_name = "" ; c_value = "" ;
		    c_signer_id = "" ; c_signature = SIG_OK } in
  let c = d.get_commit id in
  let cert_list =
    try
      let tag = List.assoc id d.tags in
      [ { fake_cert with c_name = "tag" ; c_value = tag } ]
    with Not_found -> [] in
  { (fetch_revision d id) with certs =
    { fake_cert with c_name = "author" ; c_value = c.author } ::
    { fake_cert with c_name = "committer" ; c_value = c.committer } ::
    { fake_cert with c_name = "changelog" ; c_value = c.log } :: cert_list
  }

(* for autocolor by keyid *)
let fetch_cert_signer d id n = []

(* for autocolor *)
let fetch_cert_value d id = function
  | "author" -> [
      let a = (d.get_commit id).author in
      try Scanf.sscanf a "%s@>" (fun id -> id) with _ -> a
    ]
  | "tag" -> begin
      try [ List.assoc id d.tags ]
      with Not_found -> []
  end
  | _ -> []

(* find by tag *)
let get_matching_tags d p =
  List.filter
    (fun (_, t) -> p t)
    d.tags

(* find by date *)
let get_matching_dates d s = []

(* autocolor by keyid *)
let get_key_rowid d k = 0

(* diff *)
let run_monotone_diff d exe (parent, child) status cb =
  match d.git_kind with
  | `LINUS ->
      ignore (Glib.Idle.add (fun () ->
	cb (`SUB_PROC_ERROR "Diffs are not suported with git yet") ;
	false))
  | `PASKY ->
      let cmd = [ "git"; "diff"; "-r"; parent; "-r"; child] in
      log "exec" "### exec: Running '%s'" (String.concat " " cmd) ;
      let error fmt =
	Printf.kprintf (fun s -> cb (`SUB_PROC_ERROR s)) fmt in
      try
	status#push "Running git diff ..." ;
	ignore (
	Subprocess.spawn_out
	  ~working_directory:d.base
	  ~encoding:`LOCALE ~cmd
	  ~reap_callback:status#pop
	  (fun ~exceptions ~stdout ~stderr status ->
	    if status <> 0 then
	      if stderr = ""
	      then
		error "git diff exited with status %d:\n%s" status
		  (String.concat "\n" (List.map Printexc.to_string exceptions))
	      else
		error "git diff error:\n%s" stderr
	    else
	      cb (`DIFF stdout)))
      with Gspawn.Error (_, msg) ->
	Viz_types.errorf "Could not execute monotone:\n%s" msg