What makes me tick.

Friday, October 30, 2009

Fossil SCM and Emacs VM

After a few days of tinkering around, I finished the first version of the fossil-scm to emacs integration.  I posted this as a zip file on the fossil-users mailing list as well, but I don't see it in the archives.   It needs one funciton in C - add this in src/info.c, just below finfo_page.    The vc-fossil.el file should be in your load path - see the top of the file for instructions.

Code to add to src/info.c.  Then build and install fossil;  See further below for emacs lisp file.

/*
** COMMAND: finfo
** 
** Usage: %fossil finfo -l|--log ?-v|--verbose? FILENAME / -s|--status FILENAME / -p|--print ?-r|--revision REV?FILENAME 
**
** Print the complete change history for a single file going backwards
** in time.  If -l is specified the full comment is printed, otherwise
** one line is printed per revision.
**
** In the -s form prints the status as
** 
** In the -p form prints to stdout the revision of the file specified by REV
**
*/

void finfo_cmd(void){
  int vid;

  db_must_be_within_tree();
  vid = db_lget_int("checkout", 0);
  vfile_check_signature(vid);
  if (find_option("status","s",0)) {
      Stmt q;
      Blob line;
      Blob fname;

      if (g.argc != 3) {
 usage("-s|--status FILENAME");
      }
      file_tree_name(g.argv[2], &fname, 1);
      db_prepare(&q,
"SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)"
"  FROM vfile WHERE vfile.pathname=%B", &fname);
      blob_zero(&line);
      if ( db_step(&q)==SQLITE_ROW ) {
 Blob uuid;
 Blob latest;
 int isDeleted = db_column_int(&q, 1);
 int isNew = db_column_int(&q,2) == 0;
 int chnged = db_column_int(&q,3);
 int renamed = db_column_int(&q,4);
 int isLatest = 0;

 blob_zero(&uuid);
 db_blob(&uuid,"SELECT uuid FROM blob, mlink, vfile WHERE "
 "blob.rid = mlink.mid AND mlink.fid = vfile.rid AND "
 "vfile.pathname=%B",&fname);

 blob_zero(&latest);
 db_blob (&latest,
  "SELECT ci.uuid"
  "  FROM mlink, blob b, event, blob ci"
  " WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%B)"
  "   AND b.rid=mlink.fid  AND event.objid=mlink.mid"
  "   AND event.objid=ci.rid ORDER BY event.mtime DESC",
&fname
 );
 isLatest = strcmp(blob_str(&latest),blob_str(&uuid)) == 0;
 if (isNew) {
     blob_appendf(&line, "new");
 } else if (isDeleted) {
     blob_appendf(&line, "deleted");
 } else if (renamed) {
     blob_appendf(&line, "renamed");
 } else if (chnged) {
     blob_appendf(&line, "edited");
 } else {
     blob_appendf(&line, "unchanged");
 }
 blob_appendf(&line, " ");
 if (isLatest) {
     blob_appendf(&line, "latest");
 } else {
     blob_appendf(&line, "old");
 }
 blob_appendf(&line, " ");
 blob_appendf(&line, " %10.10s", blob_str(&uuid));
 blob_reset(&uuid);
 blob_reset(&latest);
      } else {
 blob_appendf(&line, "unknown 0");
      }  
      db_finalize(&q);
      printf("%s\n", blob_str(&line));
      blob_reset(&fname);
      blob_reset(&line);
  } else if (find_option("print","p",0)) {
      Blob record;
      Blob fname;
      const char *zRevision = find_option("revision", "r", 1);

      file_tree_name(g.argv[2], &fname, 1);
      if (zRevision) {
 historical_version_of_file(zRevision, blob_str(&fname), &record);
      } else {
 int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B", &fname);
 if( rid==0 ){
     fossil_fatal("no history for file: %b", &fname);
 }
 content_get(rid, &record);
      } 
      blob_write_to_file(&record, "-");
      blob_reset(&record);
      blob_reset(&fname);
  } else if (find_option("log","l",0)) {
      Blob line;
      Stmt q;
      Blob fname;
      int rid;

      int verbose = (find_option("verbose","v",0) != 0);
      if (g.argc != 3) {
 usage("-l|--log ?-v|--verbose? FILENAME");
      }
      file_tree_name(g.argv[2], &fname, 1);
      rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B", &fname);
      if( rid==0 ){
 fossil_fatal("no history for file: %b", &fname);
      }
      db_prepare(&q,
"SELECT substr(b.uuid,1,10), datetime(event.mtime,'localtime'),"
"       coalesce(event.ecomment, event.comment),"
"       coalesce(event.euser, event.user),"
"       mlink.pid, mlink.fid, mlink.mid, mlink.fnid, ci.uuid"
"  FROM mlink, blob b, event, blob ci"
" WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%B)"
"   AND b.rid=mlink.fid"
"   AND event.objid=mlink.mid"
"   AND event.objid=ci.rid"
" ORDER BY event.mtime DESC",
&fname
 );
      blob_zero(&line);
      if (verbose) {
 printf("History of %s\n", blob_str(&fname));
      }
      while( db_step(&q)==SQLITE_ROW ){
 const char *zUuid = db_column_text(&q, 0);
 const char *zDate = db_column_text(&q, 1);
 const char *zCom = db_column_text(&q, 2);
 const char *zUser = db_column_text(&q, 3);
 const char *zCkin = db_column_text(&q,8);
 blob_reset(&line);
 if (verbose) {
     blob_appendf(&line, "%.10s in %.10s by %s ", zUuid, zCkin, zUser);
     blob_appendf(&line, "at %s\n", zDate);
     blob_appendf(&line,"%s\n", zCom);
     comment_print(blob_str(&line), 4, 79);
 } else {
     blob_appendf(&line, "%.10s ", zCkin);
     blob_appendf(&line, "%.10s ", zDate);
     blob_appendf(&line, "%8.8s ", zUser);
     blob_appendf(&line,"%-40.40s\n", zCom );
     comment_print(blob_str(&line), 0, 79);
 }
      }
      db_finalize(&q);
      blob_reset(&fname);
  } else {
      usage("at least one of -p,-s,-l must be specified");
  }
}
  
vc-fossil.el file. See instructions at top of this file:

;;; vc-fossil.el --- VC backend for the fossil sofware configuraiton management system
;; Author: Venkat Iyer

;;; Commentary:

;; This file contains a VC backend for the fossil version control
;; system.
;;

;;; Installation:

;; 1. Put this file somewhere in the emacs load-path.  2. Add Fossil
;; to the list of supported backends in `vc-handled-backends'
;;
;; e.g.    (add-to-list 'vc-handled-backends 'Fossil)

;;; Implemented Functions
;; BACKEND PROPERTIES
;; * revision-granularity
;; STATE-QUERYING FUNCTIONS
;; * registered (file)   
;; * state (file) - 'up-to-date 'edited 'needs-patch 'needs-merge 
;; * workfile-version (file)
;; * checkout-model (file)
;; - workfile-unchanged-p (file)
;; STATE-CHANGING FUNCTIONS
;; * register (file &optional rev comment)
;; * checkin (file rev comment)
;; * find-version (file rev buffer)
;; * checkout (file &optional editable rev)
;; * revert (file &optional contents-done)
;; - responsible-p (file)
;; HISTORY FUNCTIONS
;; * print-log (file &optional buffer)
;; * diff (file &optional rev1 rev2 buffer)
;; MISCELLANEOUS
;; - delete-file (file)
;; - rename-file (old new)

(eval-when-compile (require 'cl) (require 'vc))

;;; BACKEND PROPERTIES

(defun vc-fossil-revision-granularity ()
     'repository)

(defun vc-fossil-command (buffer okstatus file-or-list &rest flags)
  "A wrapper around `vc-do-command' for use in vc-fossil.el.
   The difference to vc-do-command is that this function always invokes `fossil'."
  (apply 'vc-do-command buffer okstatus "fossil" file-or-list flags ))


;; Should merge this with next function.

(defun vc-fossil-run (&rest args)
  "Run a fossil command on FILE and return its output as string."
  (let* ((ok t)
         (str (with-output-to-string
                (with-current-buffer standard-output
                  (unless (eq 0 (apply #'call-process "fossil" nil '(t nil) nil
                                       (append args)))
                    (setq ok nil))))))
    (and ok str)))


(defun vc-fossil-filestr (file &rest args)
  "Run a fossil command on FILE and return its output as string."
  (let* ((ok t)
         (str (with-output-to-string
                (with-current-buffer standard-output
                  (unless (eq 0 (apply #'call-process "fossil" nil '(t nil) nil
                                       (append args (list (file-relative-name file)))))
                    (setq ok nil))))))
    (and ok str)))

;;; STATE-QUERYING FUNCTIONS

(defun vc-fossil-registered (file)
  "Check whether FILE is registered with fossil."
  (with-temp-buffer
    (let* ((dir (file-name-directory file))
  (name (file-relative-name file dir)))
      (and (ignore-errors
    (when dir (cd dir))
    (eq 0 (call-process "fossil" nil '(t nil) nil "finfo" "-s" name)))
    (let ((str (buffer-string)))
      (and (not 
   (string= (substring str 0 7) "unknown"))))))))

(defun vc-fossil-state (file)
  "Fossil specific version of `vc-state'."
  (let ((state (vc-fossil-filestr file "finfo" "-s")))
    (if (not state)
nil
      (if (string-match "unchanged" state)
 (if (string-match "latest" state)
     'up-to-date 'needs-patch)
(if (string-match "latest" state)
   'edited 'needs-merge)))))

(defun vc-fossil-workfile-version (file)
  "Fossil specific version of `vc-workfile-version'."
  (let ((state (vc-fossil-filestr file "finfo" "-s")))
    (if (not state)
nil
      (car (cdr (cdr (split-string state)))))))

(defun vc-fossil-checkout-model (file)
  'implicit)

(defun vc-fossil-workfile-unchanged-p (file)
  (eq 'up-to-date (vc-fossil-state file)))

;;; STATE-CHANGING FUNCTIONS

(defun vc-fossil-create-repo ()
  "Create a new Fossil Repository."
  (vc-fossil-command nil 0 nil "new"))

;; We ignore the comment.  There's no comment on add.
(defun vc-fossil-register (files &optional rev comment)
  "Register FILE into the fossil version-control system."
  (vc-fossil-command nil 0 files "add"))

(defun vc-fossil-responsible-p (file)
  "Check whether FILE (directory) is handled by fossil."
  (let ((dir (if (file-directory-p file) file (file-name-directory file))))
(and (ignore-errors
     (when dir (cd dir))
     (if (vc-fossil-run "info") t)))))

(defun vc-fossil-unregister (file)
  (vc-fossil-command nil 0 file "rm"))


(defun vc-fossil-checkin (files rev comment)
  (vc-fossil-command nil 0 files "commit" "-m" comment))


(defun vc-fossil-find-version (file rev buffer)
  (if (string= rev "")
      (vc-fossil-command buffer 0 file "finfo" "-p")
      (vc-fossil-command buffer 0 file "finfo" "-r" rev "-p")))

(defun vc-fossil-checkout (file &optional editable rev)
  (if (eq rev t)
      (vc-fossil-command nil 0 nil "update")
    ((vc-fossil-command nil 0 nil "update" rev)
  )))

(defun vc-fossil-revert (file &optional contents-done)
  "Revert FILE to the version stored in the fossil repository."
  (if contents-done t
    (vc-fossil-command nil 0 file "revert" "--yes")))

;; HISTORY FUNCTIONS

(defun vc-fossil-print-log (file &optional buffer)
  "Print full log for a file"
  (vc-fossil-command buffer 0 file "finfo" "-l"))

(defun vc-fossil-diff (file &optional rev1 rev2 buffer)
  "Get Differences for a file"
  (if (and rev1 rev2)
      (error "Can't handle 2 revisions in diff <%s> and <%s>" rev1 rev2)
    (let ((buf (or buffer "*vc-diff*")))
      (vc-fossil-command buf 0 file "diff" "-i" "-r" rev1))))

;;; MISCELLANEOUS

(defun vc-fossil-delete-file (file)
  (vc-fossil-command nil 0 file "rm"))

(defun vc-fossil-rename-file (old new)
  (vc-fossil-command nil 0 (list old new) "mv"))

(provide 'vc-fossil)

Followers