Venks at it

What makes me tick.

Wednesday, March 24, 2010

Print Decimal as hex.

I'm working on an embedded system, where the printf only supports %x,%s and %c. To keep my logs human-readable, I'd like many of the numbers to be printed in decimal.  Here's one simple, elegant, but inefficient (and with limitations) version.   I use a function to convert the decimal to a (bigger) hex number that would print right when used with %x.  The generated number is otherwise useless.  This is small enough for the footprint to be tolerable, even in my embedded system:


int dize(int i) {
    int r = 0, p = 1;
    while (i > 0) {
        r += i % 10 * p;
        i /= 10;
        p *= 16;
    }
    return r;
}

And a sample usage:

main () {
    printf("%x\n", 2010);
    printf("%x\n", dize(2010));
}

Would print:

7da
2010

Friday, November 06, 2009

Mingw stat broken?

I had some unexplained failures in fossil-under-vc-under-dired on windows.  Finally tracked it down to the fact that file_isdir was failing under mingw.   For directory names including the trailing "/", stat does not set the ISDIR flag.  There's some on the topic in http://osdir.com/ml/bug-gnulib-gnu/2009-09/msg00153.html


 int file_isdir(const char *zFilename){
   struct stat buf;
+#ifdef __MINGW32__
+  /* mingw has a bug where a trailing slash causes ISDIR to be zero */
+  Blob  SaneName;
+
+  file_canonical_name(zFilename, &SaneName);
+  if( stat(blob_str(&SaneName), &buf)!=0 ){
+      blob_reset(&SaneName);
+      return 0;
+  }
+  blob_reset(&SaneName);
+#else
   if( stat(zFilename, &buf)!=0 ){
     return 0;
   }
+#endif
   return S_ISDIR(buf.st_mode) ? 1 : 2;
 }

Wednesday, November 04, 2009

Incremental Changes in Fossil-SCM Integration into Emacs

The --local flag was probably a bad idea.  So it's gone.  Now -n|--nochange implies that autosync is not performed.   Something else broke everything.  The file_tree_name errors out if the name of the directory which is the root of the checkout is passed in as the filename.  Here's the mod to make it return an empty
string if the root is passed in.  Without this, the call to get the tree at the root would fail with a "out of checkout tree" message.

--- src/file.c
+++ src/file.c
@@ -356,23 +356,26 @@
 ** false, then simply return 0.
 **
 ** The root of the tree is defined by the g.zLocalRoot variable.
 */
 int file_tree_name(const char *zOrigName, Blob *pOut, int errFatal){
-  int n;
+  int m,n;
   Blob full;
   db_must_be_within_tree();
   file_canonical_name(zOrigName, &full);
   n = strlen(g.zLocalRoot);
-  if( blob_size(&full)<=n || memcmp(g.zLocalRoot, blob_buffer(&full), n) ){
+  m = blob_size(&full);
+  if( m
     blob_reset(&full);
     if( errFatal ){
       fossil_fatal("file outside of checkout tree: %s", zOrigName);
     }
     return 0;
   }
   blob_zero(pOut);
+  if (m == n - 1)
+      return 1;
   blob_append(pOut, blob_buffer(&full)+n, blob_size(&full)-n);
   return 1;
 }

 

Monday, November 02, 2009

Providing vc-dir-state from vc-fossil.el

Since then I did a bunch of modifications.  What I didn't like at all was the total hack that needs-merge and needs-patch turned out to be.   After posting a stupid question on fossil-users, I realized what I really wanted was to know if the file would update if the fossil update command was invoked.  So I changed vc-fossil-state to use update.   Also added a vc-fossil-dir-state to make the VC under dired go a lot faster.

Found out last night that the vc-*-dir-state is called for every directory in the tree and is expected to return the status for only the files in that dir, not for the subdirs.

So I changed the update command to add these flags.

  • -n | --nochange : do not change any files, but go through the motions.
  • --local                : do not autosync even if autosync is on in the settings
  • -v                       : verbose, print messages even for UNCHANGED and EDITED
  • --file name       : only print status for file name.  If name is a directory, print status for files in that directory, but not for subdirectories.

Now C-x v d on the fossil tree takes a few seconds.   There's more speedup that can be done in update.c to filter out the subset earlier in the function if the --file flag is specified.

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)

Monday, October 26, 2009

File History on Fossil SCM command line

I eval'ed a bunch of distributed version control systems, and settled on fossil (www.fossil-scm.org). It helped that it's the successor to cvstrac. I am impressed with how easy it makes distributed development.   One of the things I miss sorely is the integration of fossil into emacs.   I delved some into the fossil source code.  Here's one of my first contributions (original at http://www.mail-archive.com/fossil-users@lists.fossil-scm.org/msg00665.html).

2009-Oct-27:  added error checking and -l option for verbose listings

In src/info.c add this code just before finfo_page.   Then  fossil finfo filename gives you the change history for a file.


/*
** COMMAND: finfo
** 
** Usage: %fossil finfo ?-l|--long? 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.
**
*/


void finfo_cmd(void){
  Stmt q;
  int rid;
  int verbose = 0; /* 1 means long listing */
  Blob fname;
  Blob line;
  const char *zFilename;


  db_must_be_within_tree();
  if (g.argc<3) {
      usage("FILENAME");
  }
  if (find_option("long","l",0))
      verbose = 1;
  file_tree_name(g.argv[2], &fname, 1);
  zFilename = blob_str(&fname);
  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=%Q)"
    "   AND b.rid=mlink.fid"
    "   AND event.objid=mlink.mid"
    "   AND event.objid=ci.rid"
    " ORDER BY event.mtime DESC",
    zFilename
  );
  blob_zero(&line);
  printf("History of %s\n", zFilename);
  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);
      blob_reset(&line);
      if (verbose) {
  blob_appendf(&line, "%.10s by %s ", zUuid, 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 ", zUuid);
  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);
}


Followers