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)