View Ticket
Not logged in
Ticket UUID: 7a27e10f1fe932d696f1c1b033aae8458c32e203
Title: Need a command to find states of files for entire tree/subdir for Editors and IDEs.
Status: Open Type: Feature_Request
Severity: Important Priority:
Subsystem: Resolution: Open
Last Modified: 2009-12-17 14:54:34
Version Found In: 70656d00f
Description & Comments:
This has been discussed a few times in the mailing list. My particular requirement is for integration into emacs-23. The vc-dired mode requires a command to run to give the status for each file that is registered with fossil. At present "fossil ls" gives you a list of files and their state, but it doesn't tell you if a file will change if you do an update. My users are used to having emacs tell them what all will change if they do an update.

The changes are to 3 files:

1. file.c: file_tree_name does not handle a trailing / on root dir.

2. finfo.c : added options to command line finfo: -l|--log (default), -b|--brief (only one line per revision) (optional) -s|--status: report checkin version for file. -p <revision>: print a specific version to stdout

3. update.c : added two flags: -v|--verbose : print status even for unchanged files -n|--nochange : don't do remote-pull, and print changes rather than applying them. I only need both flags together so they can be merged.

I'll attach a patch to this ticket off the current tip.


anonymous claiming to be Venkat added on 2009-12-14 20:48:03:
Patch is:

venkat:../head/fossil:37> fossil diff
Index: src/file.c
===================================================================
--- src/file.c
+++ src/file.c
@@ -391,23 +391,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<n-1 || memcmp(g.zLocalRoot, blob_buffer(&full), n-1) ){
     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;
 }
 
 /*

Index: src/finfo.c
===================================================================
--- src/finfo.c
+++ src/finfo.c
@@ -27,71 +27,169 @@
 #include "finfo.h"
 
 /*
 ** COMMAND: finfo
 **
-** Usage: %fossil finfo FILENAME
+** Usage: %fossil finfo {?-l|--log? / -s|--status / --p|--print} REV?FILENAME
+**
+** Print the complete change history for a single file going backwards
+** in time.  The default is -l.
+**
+** For the -l|--log option: If "-b|--brief" is specified one line per revision
+** is printed, otherwise the full comment is printed.  The "--limit N"
+** and "--offset P" options limits the output to the first N changes
+** after skipping P changes.
 **
-** Print the change history for a single file.
+** In the -s form prints the status as <status> <revision>.  This is
+** a quick status and does not check for up-to-date-ness of the file.
 **
-** The "--limit N" and "--offset P" options limits the output to the first
-** N changes after skipping P changes.
+** The -p form, there's an optional flag "-r|--revision REVISION".  The
+** specified version (or the latest checked out version) is printed to
+** stdout.
+**
 */
+
 void finfo_cmd(void){
-  Stmt q;
-  int vid;
-  Blob dest;
-  const char *zFilename;
-  const char *zLimit;
-  const char *zOffset;
-  int iLimit, iOffset;
+  int vid;
 
   db_must_be_within_tree();
   vid = db_lget_int("checkout", 0);
   if( vid==0 ){
     fossil_panic("no checkout to finfo files in");
   }
-  zLimit = find_option("limit",0,1);
-  iLimit = zLimit ? atoi(zLimit) : -1;
-  zOffset = find_option("offset",0,1);
-  iOffset = zOffset ? atoi(zOffset) : 0;
-  if (g.argc<3) {
-    usage("FILENAME");
-  }
-  file_tree_name(g.argv[2], &dest, 1);
-  zFilename = blob_str(&dest);
-  db_prepare(&q,
-    "SELECT b.uuid, ci.uuid, date(event.mtime,'localtime'),"
-    "       coalesce(event.ecomment, event.comment),"
-    "       coalesce(event.euser, event.user)"
-    "  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 LIMIT %d OFFSET %d",
-    zFilename, iLimit, iOffset
-  );
+  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;
+	  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);
+
+	  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);
+	  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, " ");
+	  blob_appendf(&line, " %10.10s", blob_str(&uuid));
+	  blob_reset(&uuid);
+      } else {
+	  blob_appendf(&line, "unknown 0000000000");
+      }
+      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 {
+      Blob line;
+      Stmt q;
+      Blob fname;
+      int rid;
+      const char *zFilename;
+      const char *zLimit;
+      const char *zOffset;
+      int iLimit, iOffset, iBrief;
 
-  printf("History of %s\n", zFilename);
-  while( db_step(&q)==SQLITE_ROW ){
-    const char *zFileUuid = db_column_text(&q, 0);
-    const char *zCiUuid = db_column_text(&q, 1);
-    const char *zDate = db_column_text(&q, 2);
-    const char *zCom = db_column_text(&q, 3);
-    const char *zUser = db_column_text(&q, 4);
-    char *zOut;
-    printf("%s ", zDate);
-    zOut = sqlite3_mprintf("[%.10s] %s (user: %s, artifact: [%.10s])",
-                            zCiUuid, zCom, zUser, zFileUuid);
-    comment_print(zOut, 11, 79);
-    sqlite3_free(zOut);
+      if (find_option("log","l",0)) { /* this is the default, no-op */
+      }
+      zLimit = find_option("limit",0,1);
+      iLimit = zLimit ? atoi(zLimit) : -1;
+      zOffset = find_option("offset",0,1);
+      iOffset = zOffset ? atoi(zOffset) : 0;
+      iBrief = (find_option("brief","b",0) == 0);
+      if (g.argc != 3) {
+	  usage("?-l|--log? ?-b|--brief? 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);
+      }
+      zFilename = blob_str(&fname);
+      db_prepare(&q,
+		 "SELECT b.uuid, ci.uuid, date(event.mtime,'localtime'),"
+		 "       coalesce(event.ecomment, event.comment),"
+		 "       coalesce(event.euser, event.user)"
+		 "  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 LIMIT %d OFFSET %d",
+		 zFilename, iLimit, iOffset
+	  );
+      blob_zero(&line);
+      if (iBrief) {
+	  printf("History of %s\n", blob_str(&fname));
+      }
+      while( db_step(&q)==SQLITE_ROW ){
+	  const char *zFileUuid = db_column_text(&q, 0);
+	  const char *zCiUuid = db_column_text(&q,1);
+	  const char *zDate = db_column_text(&q, 2);
+	  const char *zCom = db_column_text(&q, 3);
+	  const char *zUser = db_column_text(&q, 4);
+	  char *zOut;
+	  if (iBrief) {
+	      printf("%s ", zDate);
+	      zOut = sqlite3_mprintf("[%.10s] %s (user: %s, artifact: [%.10s])",
+				     zCiUuid, zCom, zUser, zFileUuid);
+	      comment_print(zOut, 11, 79);
+	      sqlite3_free(zOut);
+	  } else {
+	      blob_reset(&line);
+	      blob_appendf(&line, "%.10s ", zCiUuid);
+	      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);
   }
-  db_finalize(&q);
-  blob_reset(&dest);
-}
-
+}
 
 /*
 ** WEBPAGE: finfo
 ** URL: /finfo?name=FILENAME
 **

Index: src/update.c
===================================================================
--- src/update.c
+++ src/update.c
@@ -46,21 +46,40 @@
 ** single leaf.  If there are a multiple leaves, the latest is used
 ** if the --latest flag is present.
 **
 ** This command is different from the "checkout" in that edits are
 ** not overwritten.  Edits are merged into the new version.
+**
+** If the "-n|--nochange" flag is specified, There is no auto-sync-pull,
+** and no local files are modified.  But it will still go through the
+** files and print the status of files that are not up-to-date.
+**
+** If the "-v|--verbose" flag is specified, then it prints the status
+** of the unmodified and up-to-date files as well.
+**
 */
 void update_cmd(void){
   int vid;              /* Current version */
   int tid=0;            /* Target version - version we are changing to */
   Stmt q;
   int latestFlag;       /* Pick the latest version if true */
   int forceFlag;        /* True force the update */
+  int nochangeFlag;	/* Do not modify any files other than repository */
+  int verboseFlag;	/* Print states of all files */
+  const char *zFile;    /* Name of file to update */
+  Blob fname;
 
   url_proxy_options();
   latestFlag = find_option("latest",0, 0)!=0;
   forceFlag = find_option("force","f",0)!=0;
+  nochangeFlag = find_option("nochange","n",0)!= 0;
+  verboseFlag = find_option("verbose","v",0)!= 0;
+  zFile = find_option("file",0,1);
+  if (zFile != 0) {
+      file_tree_name(zFile, &fname, 1);
+  }
+
   if( g.argc!=3 && g.argc!=2 ){
     usage("?VERSION?");
   }
   db_must_be_within_tree();
   vid = db_lget_int("checkout", 0);
@@ -78,11 +97,20 @@
     }
     if( !is_a_version(tid) ){
       fossil_fatal("not a version: %s", g.argv[2]);
     }
   }
-  autosync(AUTOSYNC_PULL);
+
+  if(nochangeFlag == 0){
+    /*
+    ** Do an autosync pull prior to the update, if autosync is on and they
+    ** did not want a specific version (i.e. another branch, a past revision).
+    ** By not giving a specific version, they are asking for the latest, thus
+    ** pull to get the latest, then update.
+    */
+    autosync(AUTOSYNC_PULL);
+  }
 
   if( tid==0 ){
     compute_leaves(vid, 1);
     if( !latestFlag && db_int(0, "SELECT count(*) FROM leaves")>1 ){
       db_prepare(&q,
@@ -98,13 +126,16 @@
     tid = db_int(0, "SELECT rid FROM leaves, event"
                     " WHERE event.objid=leaves.rid"
                     " ORDER BY event.mtime DESC");
   }
 
-  db_begin_transaction();
+  if (!nochangeFlag) {
+      db_begin_transaction();
+  }
   vfile_check_signature(vid);
-  undo_begin();
+  if (!nochangeFlag)
+      undo_begin();
   load_vfile_from_rid(tid);
 
   /*
   ** The record.fn field is used to match files against each other.  The
   ** FV table contains one row for each each unique filename in
@@ -151,10 +182,21 @@
       id, rid, chnged, fn
     );
   }
   db_finalize(&q);
 
+  if (zFile != 0) {
+      if (file_isdir(zFile) == 1) {
+	  if (strlen(blob_str(&fname)) > 0) {
+	      db_multi_exec("DELETE FROM fv WHERE fn NOT GLOB '%q/*'",
+			    blob_str(&fname));
+	  }
+      } else {
+	  db_multi_exec("DELETE FROM fv WHERE fn <> '%q'", blob_str(&fname));
+      }
+  }
+
   db_prepare(&q,
     "SELECT fn, idv, ridv, idt, ridt, chnged FROM fv ORDER BY 1"
   );
   while( db_step(&q)==SQLITE_ROW ){
     const char *zName = db_column_text(&q, 0);
@@ -170,70 +212,94 @@
       */
       printf("CONFLICT %s\n", zName);
     }else if( idt>0 && idv==0 ){
       /* File added in the target. */
       printf("ADD %s\n", zName);
-      undo_save(zName);
-      vfile_to_disk(0, idt, 0);
+      if (!nochangeFlag) {
+	  undo_save(zName);
+	  vfile_to_disk(0, idt, 0);
+      }
+    }else if ( idt>0 && idv>0 && ridt == ridv){
+      /* We have latest version */
+	if (verboseFlag) {
+	    if (chnged) {
+		printf("EDITED %s\n", zName);
+	    } else {
+		printf("UNCHANGED %s\n", zName);
+	    }
+	}
     }else if( idt>0 && idv>0 && ridt!=ridv && chnged==0 ){
       /* The file is unedited.  Change it to the target version */
       printf("UPDATE %s\n", zName);
-      undo_save(zName);
-      vfile_to_disk(0, idt, 0);
+      if (!nochangeFlag) {
+	  undo_save(zName);
+	  vfile_to_disk(0, idt, 0);
+      }
     }else if( idt==0 && idv>0 ){
       if( ridv==0 ){
         /* Added in current checkout.  Continue to hold the file as
         ** as an addition */
-        db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv);
+	if (verboseFlag) {
+	    printf("ADDED %s\n", zName);
+	}
+	if (!nochangeFlag) {
+	    db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv);
+	}
       }else if( chnged ){
         printf("CONFLICT %s\n", zName);
       }else{
         char *zFullPath;
         printf("REMOVE %s\n", zName);
-        undo_save(zName);
-        zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
-        unlink(zFullPath);
-        free(zFullPath);
+	if (!nochangeFlag) {
+	    undo_save(zName);
+	    zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
+	    unlink(zFullPath);
+	    free(zFullPath);
+	}
       }
     }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
       /* Merge the changes in the current tree into the target version */
-      Blob e, r, t, v;
-      int rc;
-      char *zFullPath;
       printf("MERGE %s\n", zName);
-      undo_save(zName);
-      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
-      content_get(ridt, &t);
-      content_get(ridv, &v);
-      blob_zero(&e);
-      blob_read_from_file(&e, zFullPath);
-      rc = blob_merge(&v, &e, &t, &r);
-      if( rc>=0 ){
-        blob_write_to_file(&r, zFullPath);
-        if( rc>0 ){
-          printf("***** %d merge conflicts in %s\n", rc, zName);
-        }
-      }else{
-        printf("***** Cannot merge binary file %s\n", zName);
-      }
-      free(zFullPath);
-      blob_reset(&v);
-      blob_reset(&e);
-      blob_reset(&t);
-      blob_reset(&r);
-
+      if (!nochangeFlag) {
+	  Blob e, r, t, v;
+	  int rc;
+	  char *zFullPath;
+
+	  undo_save(zName);
+	  zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
+	  content_get(ridt, &t);
+	  content_get(ridv, &v);
+	  blob_zero(&e);
+	  blob_read_from_file(&e, zFullPath);
+	  rc = blob_merge(&v, &e, &t, &r);
+	  if( rc>=0 ){
+	      blob_write_to_file(&r, zFullPath);
+	      if( rc>0 ){
+		  printf("***** %d merge conflicts in %s\n", rc, zName);
+	      }
+	  }else{
+	      printf("***** Cannot merge binary file %s\n", zName);
+	  }
+	  free(zFullPath);
+	  blob_reset(&v);
+	  blob_reset(&e);
+	  blob_reset(&t);
+	  blob_reset(&r);
+      }
     }
   }
   db_finalize(&q);
 
   /*
   ** Clean up the mid and pid VFILE entries.  Then commit the changes.
   */
-  db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid);
-  manifest_to_disk(tid);
-  db_lset_int("checkout", tid);
-  db_end_transaction(0);
+  if (!nochangeFlag) {
+      db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid);
+      manifest_to_disk(tid);
+      db_lset_int("checkout", tid);
+      db_end_transaction(0);
+  }
 }
 
 
 /*
 ** Get the contents of a file within a given revision.