[28029] trunk/base

source_changes at macosforge.org source_changes at macosforge.org
Sat Aug 18 09:00:00 PDT 2007


Revision: 28029
          http://trac.macosforge.org/projects/macports/changeset/28029
Author:   sfiera at macports.org
Date:     2007-08-18 08:59:59 -0700 (Sat, 18 Aug 2007)

Log Message:
-----------
Incremental update on registry2

Also added ${prefix}/var/macports/registry to prefix.mtree for the future

Modified Paths:
--------------
    trunk/base/doc/prefix.mtree.in
    trunk/base/src/cregistry/entry.c
    trunk/base/src/cregistry/entry.h
    trunk/base/src/cregistry/registry.c
    trunk/base/src/cregistry/registry.h
    trunk/base/src/cregistry/sql.c
    trunk/base/src/registry2.0/Makefile
    trunk/base/src/registry2.0/entry.c
    trunk/base/src/registry2.0/entryobj.c
    trunk/base/src/registry2.0/tests/common.tcl
    trunk/base/src/registry2.0/tests/entry.tcl
    trunk/base/src/registry2.0/util.c
    trunk/base/src/registry2.0/util.h

Added Paths:
-----------
    trunk/base/src/registry2.0/portimage.tcl
    trunk/base/src/registry2.0/portuninstall.tcl
    trunk/base/src/registry2.0/registry_util.tcl
    trunk/base/src/registry2.0/tests/depends.tcl

Modified: trunk/base/doc/prefix.mtree.in
===================================================================
--- trunk/base/doc/prefix.mtree.in	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/doc/prefix.mtree.in	2007-08-18 15:59:59 UTC (rev 28029)
@@ -242,6 +242,8 @@
             distfiles
 	    ..
             receipts
+        ..
+            registry
             ..
         ..
     ..

Modified: trunk/base/src/cregistry/entry.c
===================================================================
--- trunk/base/src/cregistry/entry.c	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/src/cregistry/entry.c	2007-08-18 15:59:59 UTC (rev 28029)
@@ -48,11 +48,23 @@
  *
  * TODO: process the 'state' keyword in a more efficient way. I still believe
  *       there could be benefits to be reaped in the future by allowing
- *       arbitrary values but at the same time it will always have very discrete
- *       values. These could be more efficiently dealt with as integers.
+ *       arbitrary values; but at the same time the field will (or should)
+ *       always have very discrete values. These could be more efficiently dealt
+ *       with as integers.
  *
  * TODO: move the utility functions to util.h or something. Not important until
  *       there are more types in the registry than entry, though.
+ *
+ * TODO: considering a "weak" flag in registry.files. The meaning of this would
+ *       be "I wish for my version of this file to be activated when I am, but
+ *       not to be deactivated when I am; nor should other ports be prevented
+ *       from overwriting this file." This would be useful for files like e.g.
+ *       perllocal.pod (or whatever it's called; the one that causes problems).
+ *
+ * TODO: expose a file's mtime attribute. This should be used during port
+ *       deactivation to determine if any files have local modifications. If so,
+ *       they should be moved aside instead of being removed when the port is
+ *       deactivated/uninstalled.
  */
 
 /**
@@ -236,8 +248,8 @@
         char* revision, char* variants, char* epoch, reg_error* errPtr) {
     sqlite3_stmt* stmt;
     reg_entry* entry = NULL;
-    char* query = "SELECT registry.ports.id FROM registry.ports "
-        "WHERE name=? AND version=? AND revision=? AND variants=? AND epoch=?";
+    char* query = "SELECT id FROM registry.ports WHERE name=? AND version=? "
+        "AND revision=? AND variants=? AND epoch=?";
     if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
             && (sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC)
                 == SQLITE_OK)
@@ -287,17 +299,53 @@
 int reg_entry_delete(reg_entry* entry, reg_error* errPtr) {
     reg_registry* reg = entry->reg;
     int result = 0;
-    sqlite3_stmt* stmt;
-    char* query = "DELETE FROM registry.ports WHERE id=?";
-    if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
-            && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
+    sqlite3_stmt* ports;
+    sqlite3_stmt* files;
+    sqlite3_stmt* dependencies;
+    char* ports_query = "DELETE FROM registry.ports WHERE id=?";
+    char* files_query = "DELETE FROM registry.files WHERE id=?";
+    char* dependencies_query = "DELETE FROM registry.dependencies WHERE id=?";
+    if ((sqlite3_prepare(reg->db, ports_query, -1, &ports, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(ports, 1, entry->id) == SQLITE_OK)
+            && (sqlite3_prepare(reg->db, files_query, -1, &files, NULL)
+                == SQLITE_OK)
+            && (sqlite3_bind_int64(files, 1, entry->id) == SQLITE_OK)
+            && (sqlite3_prepare(reg->db, dependencies_query, -1, &dependencies,
+                    NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(dependencies, 1, entry->id) == SQLITE_OK)) {
         int r;
         do {
-            r = sqlite3_step(stmt);
+            r = sqlite3_step(ports);
             switch (r) {
                 case SQLITE_DONE:
                     if (sqlite3_changes(reg->db) > 0) {
-                        result = 1;
+                        do {
+                            r = sqlite3_step(files);
+                            switch (r) {
+                                case SQLITE_DONE:
+                                    do {
+                                        r = sqlite3_step(dependencies);
+                                        switch (r) {
+                                            case SQLITE_DONE:
+                                                result = 1;
+                                                break;
+                                            case SQLITE_BUSY:
+                                                break;
+                                            case SQLITE_ERROR:
+                                                reg_sqlite_error(reg->db,
+                                                        errPtr, NULL);
+                                                break;
+                                        }
+                                    } while (r == SQLITE_BUSY);
+                                    break;
+                                case SQLITE_BUSY:
+                                    break;
+                                case SQLITE_ERROR:
+                                    reg_sqlite_error(reg->db, errPtr, NULL);
+                                    break;
+                            }
+                        } while (r == SQLITE_BUSY);
+                        break;
                     } else {
                         errPtr->code = REG_INVALID;
                         errPtr->description = "an invalid entry was passed";
@@ -307,14 +355,16 @@
                 case SQLITE_BUSY:
                     break;
                 case SQLITE_ERROR:
-                    reg_sqlite_error(reg->db, errPtr, query);
+                    reg_sqlite_error(reg->db, errPtr, NULL);
                     break;
             }
         } while (r == SQLITE_BUSY);
     } else {
-        reg_sqlite_error(reg->db, errPtr, query);
+        reg_sqlite_error(reg->db, errPtr, NULL);
     }
-    sqlite3_finalize(stmt);
+    sqlite3_finalize(ports);
+    sqlite3_finalize(files);
+    sqlite3_finalize(dependencies);
     return result;
 }
 
@@ -432,8 +482,8 @@
     int i;
     char* kwd = " WHERE ";
     char* query;
-    int query_len = 44;
-    int query_space = 44;
+    int query_len = 29;
+    int query_space = 29;
     int result;
     /* get the strategy */
     char* op = reg_strategy_op(strategy, errPtr);
@@ -441,7 +491,7 @@
         return -1;
     }
     /* build the query */
-    query = strdup("SELECT registry.ports.id FROM registry.ports");
+    query = strdup("SELECT id FROM registry.ports");
     for (i=0; i<key_count; i++) {
         char* cond = sqlite3_mprintf(op, keys[i], vals[i]);
         reg_strcat(&query, &query_len, &query_space, kwd);
@@ -459,8 +509,11 @@
  * Finds ports which are installed as an image, and/or those which are active
  * in the filesystem. When the install mode is 'direct', this will be equivalent
  * to `reg_entry_installed`.
- * @todo add more arguments (epoch, revision, variants), maybe
  *
+ * Note that the name is a bit of a misnomer, since you can install a port with
+ * installtype direct and it will still be in this list. It's really "imaged or
+ * installed" but that's usually redundant and too long to type.
+ *
  * @param [in] reg      registry object as created by `registry_open`
  * @param [in] name     specific port to find (NULL for any)
  * @param [in] version  specific version to find (NULL for any)
@@ -468,23 +521,37 @@
  * @param [out] errPtr  description of error encountered, if any
  * @return              the number of entries if success; false if failure
  */
-int reg_entry_imaged(reg_registry* reg, char* name, char* version, 
-        reg_entry*** entries, reg_error* errPtr) {
-    char* format;
+int reg_entry_imaged(reg_registry* reg, const char* name, const char* version,
+        const char* revision, const char* variants, reg_entry*** entries,
+        reg_error* errPtr) {
     char* query;
     int result;
-    char* select = "SELECT registry.ports.id FROM registry.ports";
-    if (name == NULL) {
-        format = "%s WHERE (state='imaged' OR state='installed')";
-    } else if (version == NULL) {
-        format = "%s WHERE (state='imaged' OR state='installed') AND name='%q'";
-    } else {
-        format = "%s WHERE (state='imaged' OR state='installed') AND name='%q' "
-            "AND version='%q'";
+    char* empty = "";
+    char* name_clause = empty;
+    char* version_clause = empty;
+    char* revision_clause = empty;
+    char* variants_clause = empty;
+    if (name != NULL) {
+        name_clause = sqlite3_mprintf(" AND name='%q'", name);
     }
-    query = sqlite3_mprintf(format, select, name, version);
+    if (version != NULL) {
+        version_clause = sqlite3_mprintf(" AND version='%q'", version);
+    }
+    if (revision != NULL) {
+        revision_clause = sqlite3_mprintf(" AND revision='%q'", revision);
+    }
+    if (variants != NULL) {
+        variants_clause = sqlite3_mprintf(" AND variants='%q'", variants);
+    }
+    query = sqlite3_mprintf("SELECT id FROM ports WHERE (state='imaged' OR "
+            "state='installed')%s%s%s%s", name_clause,
+            version_clause, revision_clause, variants_clause);
     result = reg_all_entries(reg, query, -1, entries, errPtr);
     sqlite3_free(query);
+    if (name_clause != empty) sqlite3_free(name_clause);
+    if (version_clause != empty) sqlite3_free(version_clause);
+    if (revision_clause != empty) sqlite3_free(revision_clause);
+    if (variants_clause != empty) sqlite3_free(variants_clause);
     return result;
 }
 
@@ -504,7 +571,7 @@
     char* format;
     char* query;
     int result;
-    char* select = "SELECT registry.ports.id FROM registry.ports";
+    char* select = "SELECT id FROM registry.ports";
     if (name == NULL) {
         format = "%s WHERE state='installed'";
     } else {
@@ -530,9 +597,7 @@
         reg_error* errPtr) {
     int result = 0;
     sqlite3_stmt* stmt;
-    char* query = "SELECT registry.files.id FROM registry.files INNER JOIN "
-        "registry.ports USING(id) WHERE  registry.ports.state = 'installed' "
-        "AND registry.files.path=?";
+    char* query = "SELECT id FROM registry.files WHERE actual_path=? AND active";
     if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
             && (sqlite3_bind_text(stmt, 1, path, -1, SQLITE_STATIC)
                 == SQLITE_OK)) {
@@ -577,9 +642,7 @@
 sqlite_int64 reg_entry_owner_id(reg_registry* reg, char* path) {
     sqlite3_stmt* stmt;
     sqlite_int64 result = 0;
-    char* query = "SELECT registry.files.id FROM registry.files INNER JOIN "
-        "registry.ports USING(id) WHERE  registry.ports.state = 'installed' "
-        "AND registry.files.path=?";
+    char* query = "SELECT id FROM registry.files WHERE actual_path=? AND active";
     if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
             && (sqlite3_bind_text(stmt, 1, path, -1, SQLITE_STATIC)
                 == SQLITE_OK)) {
@@ -706,12 +769,14 @@
 int reg_entry_map(reg_entry* entry, char** files, int file_count,
         reg_error* errPtr) {
     reg_registry* reg = entry->reg;
+    int result = 1;
     sqlite3_stmt* stmt;
-    char* insert = "INSERT INTO registry.files (id, path) VALUES (?, ?)";
+    char* insert = "INSERT INTO registry.files (id, path, mtime, active) "
+        "VALUES (?, ?, 0, 0)";
     if ((sqlite3_prepare(reg->db, insert, -1, &stmt, NULL) == SQLITE_OK)
             && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
         int i;
-        for (i=0; i<file_count; i++) {
+        for (i=0; i<file_count && result; i++) {
             if (sqlite3_bind_text(stmt, 2, files[i], -1, SQLITE_STATIC)
                     == SQLITE_OK) {
                 int r;
@@ -725,23 +790,21 @@
                             break;
                         default:
                             reg_sqlite_error(reg->db, errPtr, insert);
-                            sqlite3_finalize(stmt);
-                            return i;
+                            result = 0;
+                            break;
                     }
                 } while (r == SQLITE_BUSY);
             } else {
                 reg_sqlite_error(reg->db, errPtr, insert);
-                sqlite3_finalize(stmt);
-                return i;
+                result = 0;
             }
         }
-        sqlite3_finalize(stmt);
-        return file_count;
     } else {
         reg_sqlite_error(reg->db, errPtr, insert);
-        sqlite3_finalize(stmt);
-        return 0;
+        result = 0;
     }
+    sqlite3_finalize(stmt);
+    return result;
 }
 
 /**
@@ -757,13 +820,14 @@
 int reg_entry_unmap(reg_entry* entry, char** files, int file_count,
         reg_error* errPtr) {
     reg_registry* reg = entry->reg;
+    int result = 1;
     sqlite3_stmt* stmt;
-    char* query = "DELETE FROM registry.files WHERE id=? AND path=?";
+    char* query = "DELETE FROM registry.files WHERE path=? AND id=?";
     if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
-            && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
+            && (sqlite3_bind_int64(stmt, 2, entry->id) == SQLITE_OK)) {
         int i;
-        for (i=0; i<file_count; i++) {
-            if (sqlite3_bind_text(stmt, 2, files[i], -1, SQLITE_STATIC)
+        for (i=0; i<file_count && result; i++) {
+            if (sqlite3_bind_text(stmt, 1, files[i], -1, SQLITE_STATIC)
                     == SQLITE_OK) {
                 int r;
                 do {
@@ -771,41 +835,97 @@
                     switch (r) {
                         case SQLITE_DONE:
                             if (sqlite3_changes(reg->db) == 0) {
-                                errPtr->code = REG_INVALID;
-                                errPtr->description = "this entry does not own "
-                                    "the given file";
-                                errPtr->free = NULL;
-                                sqlite3_finalize(stmt);
-                                return i;
+                                reg_throw(errPtr, REG_INVALID, "this entry "
+                                        "does not own the given file");
+                                result = 0;
+                            } else {
+                                sqlite3_reset(stmt);
                             }
-                            sqlite3_reset(stmt);
                             break;
                         case SQLITE_BUSY:
                             break;
                         default:
                             reg_sqlite_error(reg->db, errPtr, query);
-                            sqlite3_finalize(stmt);
-                            return i;
+                            result = 0;
+                            break;
                     }
                 } while (r == SQLITE_BUSY);
             } else {
                 reg_sqlite_error(reg->db, errPtr, query);
-                sqlite3_finalize(stmt);
-                return i;
+                result = 0;
             }
         }
+    } else {
+        reg_sqlite_error(reg->db, errPtr, query);
+        result = 0;
+    }
+    sqlite3_finalize(stmt);
+    return result;
+}
+
+/**
+ * Gets a list of files provided by the given port. These files are in the port
+ * image and do not necessarily correspond to active files on the filesystem.
+ *
+ * TODO: check that the port's installtype is image
+ *
+ * @param [in] entry   entry to get the list for
+ * @param [out] files  a list of files provided by the port
+ * @param [out] errPtr on error, a description of the error that occurred
+ * @return             the number of files if success; negative if failure
+ */
+int reg_entry_imagefiles(reg_entry* entry, char*** files, reg_error* errPtr) {
+    reg_registry* reg = entry->reg;
+    sqlite3_stmt* stmt;
+    char* query = "SELECT path FROM registry.files WHERE id=? ORDER BY path";
+    if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
+        char** result = malloc(10*sizeof(char*));
+        int result_count = 0;
+        int result_space = 10;
+        int r;
+        do {
+            char* element;
+            r = sqlite3_step(stmt);
+            switch (r) {
+                case SQLITE_ROW:
+                    element = strdup((const char*)sqlite3_column_text(stmt, 0));
+                    reg_listcat((void***)&result, &result_count, &result_space,
+                            element);
+                    break;
+                case SQLITE_DONE:
+                case SQLITE_BUSY:
+                    break;
+                default:
+                    reg_sqlite_error(reg->db, errPtr, query);
+                    break;
+            }
+        } while (r == SQLITE_ROW || r == SQLITE_BUSY);
         sqlite3_finalize(stmt);
-        return file_count;
+        if (r == SQLITE_DONE) {
+            *files = result;
+            return result_count;
+        } else {
+            int i;
+            for (i=0; i<result_count; i++) {
+                free(result[i]);
+            }
+            free(result);
+            return -1;
+        }
     } else {
         reg_sqlite_error(reg->db, errPtr, query);
         sqlite3_finalize(stmt);
-        return 0;
+        return -1;
     }
 }
 
 /**
- * Gets a list of files owned by the given port.
+ * Gets a list of files owned by the given port. These files are active in the
+ * filesystem and could be different from the port's imagefiles.
  *
+ * TODO: check that the port is active
+ *
  * @param [in] entry   entry to get the list for
  * @param [out] files  a list of files owned by the port
  * @param [out] errPtr on error, a description of the error that occurred
@@ -814,7 +934,8 @@
 int reg_entry_files(reg_entry* entry, char*** files, reg_error* errPtr) {
     reg_registry* reg = entry->reg;
     sqlite3_stmt* stmt;
-    char* query = "SELECT path FROM registry.files WHERE id=? ORDER BY path";
+    char* query = "SELECT actual_path FROM registry.files WHERE id=? "
+        "AND active ORDER BY actual_path";
     if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
             && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
         char** result = malloc(10*sizeof(char*));
@@ -858,6 +979,250 @@
 }
 
 /**
+ * Sets an entry's files as being active in the filesystem. This entry will be
+ * subsequently returned by `reg_entry_owner` on those files' path. If all files
+ * are being activated as the names they are in the registry, then `as_files`
+ * may be NULL. If they are being activated to different paths than the original
+ * files, then `as_files` should be a list of the same length as `files`.
+ *
+ * @param [in] entry      entry to assign the file to
+ * @param [in] files      a list of files to activate
+ * @param [in] as_files   NULL, or a list of paths the files are activated as
+ * @param [in] file_count number of files to activate
+ * @param [out] errPtr    on error, a description of the error that occurred
+ * @return                true if success; false if failure
+ */
+int reg_entry_activate(reg_entry* entry, char** files, char** as_files,
+        int file_count, reg_error* errPtr) {
+    reg_registry* reg = entry->reg;
+    int result = 1;
+    int i;
+    sqlite3_stmt* select;
+    sqlite3_stmt* update;
+    char* select_query = "SELECT id FROM registry.files WHERE actual_path=? "
+        "AND active";
+    char* update_query = "UPDATE registry.files SET actual_path=?, active=1 "
+        "WHERE path=? AND id=?";
+
+    /* if as_files wasn't specified, activate as the original files */
+    if (as_files == NULL) {
+        as_files = files;
+    }
+
+    if (sqlite3_prepare(reg->db, select_query, -1, &select, NULL) == SQLITE_OK){
+        if ((sqlite3_prepare(reg->db, update_query, -1, &update, NULL)
+                == SQLITE_OK)
+                && (sqlite3_bind_int64(update, 3, entry->id) == SQLITE_OK)) {
+            for (i=0; i<file_count && result; i++) {
+                if ((sqlite3_bind_text(select, 1, files[i], -1, SQLITE_STATIC)
+                            == SQLITE_OK)
+                        && (sqlite3_bind_text(update, 1, as_files[i], -1,
+                                SQLITE_STATIC) == SQLITE_OK)
+                        && (sqlite3_bind_text(update, 2, files[i], -1,
+                                SQLITE_STATIC) == SQLITE_OK)) {
+                    int r;
+                    do {
+                        r = sqlite3_step(select);
+                        switch (r) {
+                            case SQLITE_ROW:
+                                reg_throw(errPtr, REG_ALREADY_ACTIVE, "%s is "
+                                        "being used by another port", files[i]);
+                                result = 0;
+                                break;
+                            case SQLITE_DONE:
+                                do {
+                                    r = sqlite3_step(update);
+                                    switch (r) {
+                                        case SQLITE_DONE:
+                                            if (sqlite3_changes(reg->db) == 0) {
+                                                reg_throw(errPtr, REG_INVALID,
+                                                        "%s is not provided by "
+                                                        "this port", files[i]);
+                                                result = 0;
+                                            } else {
+                                                sqlite3_reset(select);
+                                                sqlite3_reset(update);
+                                            }
+                                            break;
+                                        case SQLITE_BUSY:
+                                            break;
+                                        case SQLITE_ERROR:
+                                            reg_sqlite_error(reg->db, errPtr,
+                                                    update_query);
+                                            result = 0;
+                                            break;
+                                    }
+                                } while (r == SQLITE_BUSY);
+                                break;
+                            case SQLITE_BUSY:
+                                break;
+                            case SQLITE_ERROR:
+                                reg_sqlite_error(reg->db, errPtr, select_query);
+                                result = 0;
+                                break;
+                        }
+                    } while (r == SQLITE_BUSY);
+                } else {
+                    reg_sqlite_error(reg->db, errPtr, NULL);
+                    result = 0;
+                }
+            }
+        } else {
+            reg_sqlite_error(reg->db, errPtr, update_query);
+            result = 0;
+        }
+        sqlite3_finalize(update);
+    } else {
+        reg_sqlite_error(reg->db, errPtr, select_query);
+        result = 0;
+    }
+    sqlite3_finalize(select);
+    return result;
+}
+
+/**
+ * Deactivates files owned by a given entry. That entry's version of all files
+ * must currently be active.
+ * 
+ * @param [in] entry      current owner of the files
+ * @param [in] files      a list of files to deactivate
+ * @param [in] file_count number of files to deactivate
+ * @param [out] errPtr    on error, a description of the error that occurred
+ * @return                true if success; false if failure
+ */
+int reg_entry_deactivate(reg_entry* entry, char** files, int file_count,
+        reg_error* errPtr) {
+    reg_registry* reg = entry->reg;
+    int result = 1;
+    int i;
+    sqlite3_stmt* stmt;
+    char* query = "UPDATE registry.files SET active=0 WHERE actual_path=? AND id=?";
+    if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 2, entry->id) == SQLITE_OK)) {
+        for (i=0; i<file_count && result; i++) {
+            if (sqlite3_bind_text(stmt, 1, files[i], -1, SQLITE_STATIC)
+                    == SQLITE_OK) {
+                int r;
+                do {
+                    r = sqlite3_step(stmt);
+                    switch (r) {
+                        case SQLITE_DONE:
+                            if (sqlite3_changes(reg->db) == 0) {
+                                reg_throw(errPtr, REG_INVALID, "this entry "
+                                        "does not own the given file");
+                                result = 0;
+                            } else {
+                                sqlite3_reset(stmt);
+                            }
+                            break;
+                        case SQLITE_BUSY:
+                            break;
+                        default:
+                            reg_sqlite_error(reg->db, errPtr, query);
+                            result = 0;
+                            break;
+                    }
+                } while (r == SQLITE_BUSY);
+            } else {
+                reg_sqlite_error(reg->db, errPtr, query);
+                result = 0;
+            }
+        }
+    } else {
+        reg_sqlite_error(reg->db, errPtr, query);
+        result = 0;
+    }
+    sqlite3_finalize(stmt);
+    return result;
+}
+
+/**
+ * Gets a list of ports that depend on this one. Uninstalling the given port
+ * could potentially break any port listed in its dependents, and could not
+ * break any other.
+ *
+ * N.B.: an inactive port has no dependents, since it can be safely removed.
+ *
+ * @param [in] entry       a port
+ * @param [out] dependents a list of ports dependent on the given port
+ * @param [out] errPtr     on error, a description of the error that occurred
+ * @return                 true if success; false if failure
+ */
+int reg_entry_dependents(reg_entry* entry, reg_entry*** dependents,
+        reg_error* errPtr) {
+    reg_registry* reg = entry->reg;
+    char* query = sqlite3_mprintf("SELECT dependent.id FROM ports port "
+            "INNER JOIN dependencies USING(name) INNER JOIN ports dependent "
+            "USING(id) WHERE port.id=%lld AND port.state = 'installed' "
+            "AND dependent.state = 'installed'",
+            entry->id);
+    int result = reg_all_entries(reg, query, -1, dependents, errPtr);
+    sqlite3_free(query);
+    return result;
+}
+
+/**
+ * Gets a list of ports that this one depends on. This only returns ports which
+ * have state "installed" and should really only be called upon ports which have
+ * state "installed" themselves. Uninstalling any port in this list could break
+ * the given port, but uninstalling any other could not break it.
+ *
+ * @param [in] entry         a port
+ * @param [out] dependencies a list of ports the given port depends on
+ * @param [out] errPtr       on error, a description of the error that occurred
+ * @return                   number of deps if success; negative if failure
+ */
+int reg_entry_dependencies(reg_entry* entry, reg_entry*** dependencies,
+        reg_error* errPtr) {
+    reg_registry* reg = entry->reg;
+    char* query = sqlite3_mprintf("SELECT ports.id FROM registry.dependencies "
+        "INNER JOIN registry.ports USING(name) WHERE dependencies.id=%lld AND "
+        "ports.state = 'installed'", entry->id);
+    int result = reg_all_entries(reg, query, -1, dependencies, errPtr);
+    sqlite3_free(query);
+    return result;
+}
+
+/**
+ * Sets the given port to depend on the named port. This is a weak link; it
+ * refers to a name and not an actual port.
+ *
+ * @param [in] entry   a port
+ * @param [in] name    the name of a port the given port depends on
+ * @param [out] errPtr on error, a description of the error that occurred
+ * @return             true if success; false if failure
+ */
+int reg_entry_depends(reg_entry* entry, char* name, reg_error* errPtr) {
+    reg_registry* reg = entry->reg;
+    int result = 0;
+    sqlite3_stmt* stmt;
+    char* query = "INSERT INTO registry.dependencies (id, name) VALUES (?,?)";
+    if ((sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)
+            && (sqlite3_bind_text(stmt, 2, name, -1, SQLITE_STATIC)
+                == SQLITE_OK)) {
+        int r;
+        do {
+            r = sqlite3_step(stmt);
+            switch (r) {
+                case SQLITE_DONE:
+                    result = 1;
+                    break;
+                case SQLITE_BUSY:
+                    break;
+                default:
+                    reg_sqlite_error(reg->db, errPtr, query);
+                    break;
+            }
+        } while (r == SQLITE_BUSY);
+    } else {
+        reg_sqlite_error(reg->db, errPtr, query);
+    }
+    sqlite3_finalize(stmt);
+    return result;
+}
+
+/**
  * Fetches a list of all open entries.
  *
  * @param [in] reg      registry to fetch entries from

Modified: trunk/base/src/cregistry/entry.h
===================================================================
--- trunk/base/src/cregistry/entry.h	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/src/cregistry/entry.h	2007-08-18 15:59:59 UTC (rev 28029)
@@ -60,8 +60,9 @@
 int reg_entry_search(reg_registry* reg, char** keys, char** vals, int key_count,
         int strategy, reg_entry*** entries, reg_error* errPtr);
 
-int reg_entry_imaged(reg_registry* reg, char* name, char* version, 
-        reg_entry*** entries, reg_error* errPtr);
+int reg_entry_imaged(reg_registry* reg, const char* name, const char* version,
+        const char* revision, const char* variants, reg_entry*** entries,
+        reg_error* errPtr);
 int reg_entry_installed(reg_registry* reg, char* name, reg_entry*** entries,
         reg_error* errPtr);
 
@@ -79,7 +80,19 @@
         reg_error* errPtr);
 
 int reg_entry_files(reg_entry* entry, char*** files, reg_error* errPtr);
+int reg_entry_imagefiles(reg_entry* entry, char*** files, reg_error* errPtr);
 
+int reg_entry_activate(reg_entry* entry, char** files, char** as_files,
+        int file_count, reg_error* errPtr);
+int reg_entry_deactivate(reg_entry* entry, char** files, int file_count,
+        reg_error* errPtr);
+
+int reg_entry_dependents(reg_entry* entry, reg_entry*** dependents,
+        reg_error* errPtr);
+int reg_entry_dependencies(reg_entry* entry, reg_entry*** dependencies,
+        reg_error* errPtr);
+int reg_entry_depends(reg_entry* entry, char* name, reg_error* errPtr);
+
 int reg_all_open_entries(reg_registry* reg, reg_entry*** entries);
 
 #endif /* _CENTRY_H */

Modified: trunk/base/src/cregistry/registry.c
===================================================================
--- trunk/base/src/cregistry/registry.c	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/src/cregistry/registry.c	2007-08-18 15:59:59 UTC (rev 28029)
@@ -34,6 +34,7 @@
 #include <unistd.h>
 #include <stdlib.h>
 #include <libgen.h>
+#include <stdarg.h>
 #include <sqlite3.h>
 #include <sys/stat.h>
 #include <errno.h>
@@ -42,10 +43,6 @@
 #include <cregistry/sql.h>
 
 /*
- * TODO: create a better system for throwing errors. It'd be really awesome to
- *       have e.g. `reg_throw_error(code, fmt, ...)` but I don't feel like
- *       messing with varargs right now.
- *
  * TODO: maybe all the errPtrs could be made a property of `reg_registry`
  *       instead, and be destroyed with it? That would make memory-management
  *       much easier for errors, and there's no need to have more than one error
@@ -86,6 +83,16 @@
     }
 }
 
+void reg_throw(reg_error* errPtr, char* code, char* fmt, ...) {
+    va_list list;
+    va_start(list, fmt);
+    errPtr->description = sqlite3_vmprintf(fmt, list);
+    va_end(list);
+
+    errPtr->code = code;
+    errPtr->free = (reg_error_destructor*)sqlite3_free;
+}
+
 /**
  * Creates a new registry object. To start using a registry, one must first be
  * attached with `reg_attach`.
@@ -125,10 +132,8 @@
         free(reg);
         return 1;
     } else {
-        errPtr->code = REG_SQLITE_ERROR;
-        errPtr->description = sqlite3_mprintf("registry db not closed "
-                "correctly (%s)\n", sqlite3_errmsg(reg->db));
-        errPtr->free = (reg_error_destructor*)sqlite3_free;
+        reg_throw(errPtr, REG_SQLITE_ERROR, "registry db not closed correctly "
+                "(%s)\n", sqlite3_errmsg(reg->db));
         return 0;
     }
 }
@@ -149,9 +154,8 @@
     int can_write = 1; /* can write to this location */
     int result = 0;
     if (reg->status & reg_attached) {
-        errPtr->code = REG_MISUSE;
-        errPtr->description = "a database is already attached to this registry";
-        errPtr->free = NULL;
+        reg_throw(errPtr, REG_MISUSE, "a database is already attached to this "
+                "registry");
         return 0;
     }
     if (stat(path, &sb) != 0) {
@@ -208,10 +212,8 @@
         sqlite3_finalize(stmt);
         sqlite3_free(query);
     } else {
-        errPtr->code = REG_CANNOT_INIT;
-        errPtr->description = sqlite3_mprintf("port registry doesn't exist at "
+        reg_throw(errPtr, REG_CANNOT_INIT, "port registry doesn't exist at "
                 "\"%q\" and couldn't write to this location", path);
-        errPtr->free = (reg_error_destructor*)sqlite3_free;
     }
     return result;
 }
@@ -230,9 +232,7 @@
     int result = 0;
     char* query = "DETACH DATABASE registry";
     if (!(reg->status & reg_attached)) {
-        errPtr->code = REG_MISUSE;
-        errPtr->description = "no database is attached to this registry";
-        errPtr->free = NULL;
+        reg_throw(errPtr,REG_MISUSE,"no database is attached to this registry");
         return 0;
     }
     if (sqlite3_prepare(reg->db, query, -1, &stmt, NULL) == SQLITE_OK) {
@@ -275,9 +275,8 @@
  */
 static int reg_start(reg_registry* reg, const char* query, reg_error* errPtr) {
     if (reg->status & reg_transacting) {
-        errPtr->code = REG_MISUSE;
-        errPtr->description = "couldn't start transaction because a "
-            "transaction is already open";
+        reg_throw(errPtr, REG_MISUSE, "couldn't start transaction because a "
+                "transaction is already open");
         errPtr->free = NULL;
         return 0;
     } else {
@@ -333,10 +332,8 @@
  */
 static int reg_end(reg_registry* reg, const char* query, reg_error* errPtr) {
     if (!(reg->status & reg_transacting)) {
-        errPtr->code = REG_MISUSE;
-        errPtr->description = "couldn't end transaction because no transaction "
-            "is open";
-        errPtr->free = NULL;
+        reg_throw(errPtr, REG_MISUSE, "couldn't end transaction because no "
+                "transaction is open");
         return 0;
     } else {
         int r;

Modified: trunk/base/src/cregistry/registry.h
===================================================================
--- trunk/base/src/cregistry/registry.h	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/src/cregistry/registry.h	2007-08-18 15:59:59 UTC (rev 28029)
@@ -41,6 +41,7 @@
 #define REG_SQLITE_ERROR    "registry::sqlite-error"
 #define REG_MISUSE          "registry::misuse"
 #define REG_CANNOT_INIT     "registry::cannot-init"
+#define REG_ALREADY_ACTIVE  "registry::already-active"
 
 typedef void reg_error_destructor(const char* description);
 
@@ -52,6 +53,7 @@
 
 void reg_sqlite_error(sqlite3* db, reg_error* errPtr, char* query);
 void reg_error_destruct(reg_error* errPtr);
+void reg_throw(reg_error* errPtr, char* code, char* fmt, ...);
 
 typedef int (cast_function)(void* userdata, void** dst, void* src,
         reg_error* errPtr);

Modified: trunk/base/src/cregistry/sql.c
===================================================================
--- trunk/base/src/cregistry/sql.c	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/src/cregistry/sql.c	2007-08-18 15:59:59 UTC (rev 28029)
@@ -295,9 +295,16 @@
         "CREATE INDEX registry.port_state ON ports (state)",
 
         /* file map */
-        "CREATE TABLE registry.files (id, path, mtime)",
+        "CREATE TABLE registry.files (id, path, actual_path, active, mtime, "
+            "md5sum, editable)",
         "CREATE INDEX registry.file_port ON files (id)",
+        "CREATE INDEX registry.file_path ON files(path)",
+        "CREATE INDEX registry.file_actual ON files(actual_path)",
 
+        /* dependency map */
+        "CREATE TABLE registry.dependencies (id, name)",
+        "CREATE INDEX registry.dep_name ON dependencies (name)",
+
         "COMMIT",
         NULL
     };

Modified: trunk/base/src/registry2.0/Makefile
===================================================================
--- trunk/base/src/registry2.0/Makefile	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/src/registry2.0/Makefile	2007-08-18 15:59:59 UTC (rev 28029)
@@ -1,4 +1,5 @@
 # $Id$
+SRCS=		registry_util.tcl portimage.tcl portuninstall.tcl
 OBJS=       registry.o util.o \
 			entry.o entryobj.o
 			#graph.o graphobj.o
@@ -15,3 +16,12 @@
 
 test:: ${SHLIB_NAME}
 	${TCLSH} tests/entry.tcl ${SHLIB_NAME}
+	${TCLSH} tests/depends.tcl ${SHLIB_NAME}
+
+install::
+	$(INSTALL) -d -o ${DSTUSR} -g ${DSTGRP} -m ${DSTMODE} ${INSTALLDIR}
+	$(INSTALL) -o ${DSTUSR} -g ${DSTGRP} -m 444 ${SHLIB_NAME} ${INSTALLDIR}
+	$(SILENT) set -x; for file in ${SRCS}; do \
+		$(INSTALL) -o ${DSTUSR} -g ${DSTGRP} -m 444 $$file ${INSTALLDIR}/$$file; \
+	done
+	$(SILENT) $(TCLSH) ../pkg_mkindex.tcl ${INSTALLDIR}

Modified: trunk/base/src/registry2.0/entry.c
===================================================================
--- trunk/base/src/registry2.0/entry.c	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/src/registry2.0/entry.c	2007-08-18 15:59:59 UTC (rev 28029)
@@ -66,28 +66,6 @@
     entry->proc = NULL;
 }
 
-/**
- * Sets a given name to be an entry object.
- *
- * @param [in] interp  Tcl interpreter to create the entry within
- * @param [in] name    name to associate the given entry with
- * @param [in] entry   entry to associate with the given name
- * @param [out] errPtr description of error if it couldn't be set
- * @return             true if success; false if failure
- * @see set_object
- */
-static int set_entry(Tcl_Interp* interp, char* name, reg_entry* entry,
-        reg_error* errPtr) {
-    if (set_object(interp, name, entry, "entry", entry_obj_cmd, NULL,
-                errPtr)) {
-        int size = strlen(name) + 1;
-        entry->proc = malloc(size*sizeof(char));
-        memcpy(entry->proc, name, size);
-        return 1;
-    }
-    return 0;
-}
-
 static int obj_to_entry(Tcl_Interp* interp, reg_entry** entry, Tcl_Obj* obj,
         reg_error* errPtr) {
     reg_entry* result = get_entry(interp, Tcl_GetString(obj), errPtr);
@@ -99,34 +77,16 @@
     }
 }
 
+/*
 static int list_obj_to_entry(Tcl_Interp* interp, reg_entry*** entries,
         const Tcl_Obj** objv, int objc, reg_error* errPtr) {
     return recast(interp, (cast_function*)obj_to_entry, NULL, (void***)entries,
             (void**)objv, objc, errPtr);
 }
+*/
 
-static int entry_to_obj(Tcl_Interp* interp, Tcl_Obj** obj, reg_entry* entry,
-        reg_error* errPtr) {
-    if (entry->proc == NULL) {
-        char* name = unique_name(interp, "registry::entry");
-        if (!set_entry(interp, name, entry, errPtr)) {
-            free(name);
-            return 0;
-        }
-        free(name);
-    }
-    *obj = Tcl_NewStringObj(entry->proc, -1);
-    return 1;
-}
 
-static int list_entry_to_obj(Tcl_Interp* interp, Tcl_Obj*** objs,
-        reg_entry** entries, int entry_count, reg_error* errPtr) {
-    return recast(interp, (cast_function*)entry_to_obj, NULL, (void***)objs,
-            (void**)entries, entry_count, errPtr);
-}
-
-
-/**
+/*
  * registry::entry create portname version revision variants epoch
  *
  * Unlike the old registry::new_entry, revision, variants, and epoch are all
@@ -161,7 +121,7 @@
     }
 }
 
-/**
+/*
  * registry::entry delete entry
  *
  * Deletes an entry from the registry (then closes it). If this is done within a
@@ -201,7 +161,7 @@
     }
 }
 
-/**
+/*
  * registry::entry open portname version revision variants epoch ?name?
  *
  * Opens an entry matching the given parameters.
@@ -347,7 +307,7 @@
     }
 }
 
-/**
+/*
  * registry::entry exists name
  *
  * Note that this is <i>not</i> the same as entry_exists from registry1.0. This
@@ -369,7 +329,7 @@
     return TCL_OK;
 }
 
-/**
+/*
  * registry::entry imaged ?name? ?version?
  *
  * Returns a list of all ports installed as images and/or active in the
@@ -382,29 +342,29 @@
  * no analogue in 'direct' mode (it will be equivalent to `registry::entry
  * installed`). That is, these ports are available but cannot meet dependencies.
  *
+ * I would have liked to allow implicit variants, but there's no convenient way
+ * to distinguish between variants not being specified and being specified as
+ * empty. So, if a revision is specified, so must variants be.
+ *
  * TODO: add more arguments (epoch, revision, variants), maybe
  */
 static int entry_imaged(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
     reg_registry* reg = registry_for(interp, reg_attached);
-    if (objc > 4) {
-        Tcl_WrongNumArgs(interp, 2, objv, "?name? ?version?");
+    if (objc == 5 || objc > 6) {
+        Tcl_WrongNumArgs(interp, 2, objv, "?name ?version ?revision variants???");
         return TCL_ERROR;
     } else if (reg == NULL) {
         return TCL_ERROR;
     } else {
-        char* name = (objc >= 3) ? Tcl_GetString(objv[2]) : NULL;
-        char* version = (objc == 4) ? Tcl_GetString(objv[3]) : NULL;
+        const char* name = (objc >= 3) ? string_or_null(objv[2]) : NULL;
+        const char* version = (objc >= 4) ? string_or_null(objv[3]) : NULL;
+        const char* revision = (objc >= 6) ? string_or_null(objv[4]) : NULL;
+        const char* variants = (revision != 0) ? Tcl_GetString(objv[5]) : NULL;
         reg_entry** entries;
         reg_error error;
         int entry_count;
-        /* name or version of "" means not specified */
-        if (name != NULL && *name == '\0') {
-            name = NULL;
-        }
-        if (version != NULL && *version == '\0') {
-            version = NULL;
-        }
-        entry_count = reg_entry_imaged(reg, name, version, &entries, &error);
+        entry_count = reg_entry_imaged(reg, name, version, revision, variants,
+                &entries, &error);
         if (entry_count >= 0) {
             Tcl_Obj* resultObj;
             Tcl_Obj** objs;
@@ -419,7 +379,7 @@
     }
 }
 
-/**
+/*
  * registry::entry installed ?name?
  *
  * Returns a list of all installed and active ports. If `name` is specified,
@@ -462,7 +422,7 @@
 }
 
 
-/**
+/*
  */
 static int entry_owner(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
     reg_registry* reg = registry_for(interp, reg_attached);
@@ -509,7 +469,7 @@
     { NULL, NULL }
 };
 
-/**
+/*
  * registry::entry cmd ?arg ...?
  *
  * Commands manipulating port entries in the registry. This could be called

Modified: trunk/base/src/registry2.0/entryobj.c
===================================================================
--- trunk/base/src/registry2.0/entryobj.c	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/src/registry2.0/entryobj.c	2007-08-18 15:59:59 UTC (rev 28029)
@@ -85,7 +85,7 @@
         return TCL_ERROR;
     } else {
         /* ${entry} prop name value; set a new value */
-        reg_registry* reg = registry_for(interp, reg_attached | reg_can_write);
+        reg_registry* reg = registry_for(interp, reg_attached);
         if (reg == NULL) {
             return TCL_ERROR;
         }
@@ -103,35 +103,42 @@
     }
 }
 
-/*
- * ${entry} map file-list
- *
- * Maps the listed files to the port represented by ${entry}. This will throw an
- * error if a file is mapped to an already-existing file, but not a very
- * descriptive one.
- *
- * TODO: more descriptive error on duplicated file
- */
-static int entry_obj_map(Tcl_Interp* interp, reg_entry* entry, int objc,
+typedef struct {
+    char* name;
+    int (*function)(reg_entry* entry, char** files, int file_count,
+            reg_error* errPtr);
+} filemap_op;
+
+static filemap_op filemap_cmds[] = {
+    { "map", reg_entry_map },
+    { "unmap", reg_entry_unmap },
+    { "deactivate", reg_entry_deactivate },
+    { NULL, NULL }
+};
+
+static int entry_obj_filemap(Tcl_Interp* interp, reg_entry* entry, int objc,
         Tcl_Obj* CONST objv[]) {
     reg_registry* reg = registry_for(interp, reg_attached);
+    int op;
     if (objc != 3) {
-        Tcl_WrongNumArgs(interp, 1, objv, "map file-list");
+        Tcl_WrongNumArgs(interp, 2, objv, "file-list");
         return TCL_ERROR;
     } else if (reg == NULL) {
         return TCL_ERROR;
+    } else if (Tcl_GetIndexFromObjStruct(interp, objv[1], filemap_cmds,
+                sizeof(filemap_op), "cmd", 0, &op) != TCL_OK) {
+        return TCL_ERROR;
     } else {
         char** files;
         reg_error error;
         Tcl_Obj** listv;
         int listc;
-        int i;
         int result = TCL_ERROR;
         if (Tcl_ListObjGetElements(interp, objv[2], &listc, &listv) != TCL_OK) {
             return TCL_ERROR;
         }
         if (list_obj_to_string(&files, listv, listc, &error)) {
-            if (reg_entry_map(entry, files, listc, &error) == listc) {
+            if (filemap_cmds[op].function(entry, files, listc, &error)) {
                 result = TCL_OK;
             } else {
                 result = registry_failed(interp, &error);
@@ -144,32 +151,117 @@
     }
 }
 
-/*
- * ${entry} unmap file-list
- *
- * Unmaps the listed files from the given port. Will throw an error if a file
- * that is not mapped to the port is attempted to be unmapped.
- */
-static int entry_obj_unmap(Tcl_Interp* interp, reg_entry* entry, int objc,
+static int entry_obj_files(Tcl_Interp* interp, reg_entry* entry, int objc,
         Tcl_Obj* CONST objv[]) {
     reg_registry* reg = registry_for(interp, reg_attached);
-    if (objc != 3) {
-        Tcl_WrongNumArgs(interp, 1, objv, "map file-list");
+    if (objc != 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "files");
         return TCL_ERROR;
     } else if (reg == NULL) {
         return TCL_ERROR;
     } else {
         char** files;
         reg_error error;
+        int file_count = reg_entry_files(entry, &files, &error);
+        int i;
+        if (file_count >= 0) {
+            Tcl_Obj** objs;
+            int retval = TCL_ERROR;
+            if (list_string_to_obj(&objs, files, file_count, &error)) {
+                Tcl_Obj* result = Tcl_NewListObj(file_count, objs);
+                Tcl_SetObjResult(interp, result);
+                free(objs);
+                retval = TCL_OK;
+            } else {
+                retval = registry_failed(interp, &error);
+            }
+            for (i=0; i<file_count; i++) {
+                free(files[i]);
+            }
+            free(files);
+            return retval;
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+static int entry_obj_imagefiles(Tcl_Interp* interp, reg_entry* entry, int objc,
+        Tcl_Obj* CONST objv[]) {
+    reg_registry* reg = registry_for(interp, reg_attached);
+    if (objc != 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "files");
+        return TCL_ERROR;
+    } else if (reg == NULL) {
+        return TCL_ERROR;
+    } else {
+        char** files;
+        reg_error error;
+        int file_count = reg_entry_imagefiles(entry, &files, &error);
+        int i;
+        if (file_count >= 0) {
+            Tcl_Obj** objs;
+            int retval = TCL_ERROR;
+            if (list_string_to_obj(&objs, files, file_count, &error)) {
+                Tcl_Obj* result = Tcl_NewListObj(file_count, objs);
+                Tcl_SetObjResult(interp, result);
+                free(objs);
+                retval = TCL_OK;
+            } else {
+                retval = registry_failed(interp, &error);
+            }
+            for (i=0; i<file_count; i++) {
+                free(files[i]);
+            }
+            free(files);
+            return retval;
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+static int entry_obj_activate(Tcl_Interp* interp, reg_entry* entry, int objc,
+        Tcl_Obj* CONST objv[]) {
+    reg_registry* reg = registry_for(interp, reg_attached);
+    if (objc > 4) {
+        Tcl_WrongNumArgs(interp, 1, objv, "activate file-list ?as-file-list?");
+        return TCL_ERROR;
+    } else if (reg == NULL) {
+        return TCL_ERROR;
+    } else {
+        char** files;
+        char** as_files = NULL;
+        reg_error error;
+        Tcl_Obj* as;
+        Tcl_Obj** as_listv;
         Tcl_Obj** listv;
         int listc;
-        int i;
+        int as_listc;
         int result = TCL_ERROR;
+        if (objc >= 4) {
+            as = objv[3];
+        } else {
+            as = NULL;
+            as_listv = NULL;
+        }
         if (Tcl_ListObjGetElements(interp, objv[2], &listc, &listv) != TCL_OK) {
             return TCL_ERROR;
         }
-        if (list_obj_to_string(&files, listv, listc, &error)) {
-            if (reg_entry_unmap(entry, files, listc, &error) == listc) {
+        if (as != NULL) {
+            if (Tcl_ListObjGetElements(interp, as, &as_listc, &as_listv)
+                    != TCL_OK) {
+                return TCL_ERROR;
+            }
+            if (listc != as_listc) {
+                /* TODO: set an error code */
+                Tcl_SetResult(interp, "list and as_list must be of equal "
+                        "length", TCL_STATIC);
+                return TCL_ERROR;
+            }
+        }
+        if (list_obj_to_string(&files, listv, listc, &error)
+                && (as_listv == NULL || list_obj_to_string(&as_files, as_listv,
+                        as_listc, &error))) {
+            if (reg_entry_activate(entry, files, as_files, listc, &error)) {
                 result = TCL_OK;
             } else {
                 result = registry_failed(interp, &error);
@@ -182,40 +274,84 @@
     }
 }
 
-static int entry_obj_files(Tcl_Interp* interp, reg_entry* entry, int objc,
+static int entry_obj_dependencies(Tcl_Interp* interp, reg_entry* entry,
+        int objc, Tcl_Obj* CONST objv[]) {
+    reg_registry* reg = registry_for(interp, reg_attached);
+    if (objc != 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "dependents");
+        return TCL_ERROR;
+    } else if (reg == NULL) {
+        return TCL_ERROR;
+    } else {
+        reg_entry** entries;
+        reg_error error;
+        int entry_count = reg_entry_dependencies(entry, &entries, &error);
+        if (entry_count >= 0) {
+            Tcl_Obj** objs;
+            int retval = TCL_ERROR;
+            if (list_entry_to_obj(interp, &objs, entries, entry_count, &error)){
+                Tcl_Obj* result = Tcl_NewListObj(entry_count, objs);
+                Tcl_SetObjResult(interp, result);
+                free(objs);
+                retval = TCL_OK;
+            } else {
+                retval = registry_failed(interp, &error);
+            }
+            free(entries);
+            return retval;
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+static int entry_obj_dependents(Tcl_Interp* interp, reg_entry* entry, int objc,
         Tcl_Obj* CONST objv[]) {
     reg_registry* reg = registry_for(interp, reg_attached);
     if (objc != 2) {
-        Tcl_WrongNumArgs(interp, 1, objv, "files");
+        Tcl_WrongNumArgs(interp, 1, objv, "dependents");
         return TCL_ERROR;
     } else if (reg == NULL) {
         return TCL_ERROR;
     } else {
-        char** files;
+        reg_entry** entries;
         reg_error error;
-        int file_count = reg_entry_files(entry, &files, &error);
-        if (file_count >= 0) {
-            int i;
+        int entry_count = reg_entry_dependents(entry, &entries, &error);
+        if (entry_count >= 0) {
             Tcl_Obj** objs;
             int retval = TCL_ERROR;
-            if (list_string_to_obj(&objs, files, file_count, &error)) {
-                Tcl_Obj* result = Tcl_NewListObj(file_count, objs);
+            if (list_entry_to_obj(interp, &objs, entries, entry_count, &error)){
+                Tcl_Obj* result = Tcl_NewListObj(entry_count, objs);
                 Tcl_SetObjResult(interp, result);
                 free(objs);
                 retval = TCL_OK;
             } else {
                 retval = registry_failed(interp, &error);
             }
-            for (i=0; i<file_count; i++) {
-                free(files[i]);
-            }
-            free(files);
+            free(entries);
             return retval;
         }
         return registry_failed(interp, &error);
     }
 }
 
+static int entry_obj_depends(Tcl_Interp* interp, reg_entry* entry, int objc,
+        Tcl_Obj* CONST objv[]) {
+    reg_registry* reg = registry_for(interp, reg_attached);
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 1, objv, "depends portname");
+        return TCL_ERROR;
+    } else if (reg == NULL) {
+        return TCL_ERROR;
+    } else {
+        char* port = Tcl_GetString(objv[2]);
+        reg_error error;
+        if (reg_entry_depends(entry, port, &error)) {
+            return TCL_OK;
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
 typedef struct {
     char* name;
     int (*function)(Tcl_Interp* interp, reg_entry* entry, int objc,
@@ -223,6 +359,7 @@
 } entry_obj_cmd_type;
 
 static entry_obj_cmd_type entry_cmds[] = {
+    /* keys */
     { "name", entry_obj_prop },
     { "portfile", entry_obj_prop },
     { "url", entry_obj_prop },
@@ -235,9 +372,17 @@
     { "date", entry_obj_prop },
     { "state", entry_obj_prop },
     { "installtype", entry_obj_prop },
-    { "map", entry_obj_map },
-    { "unmap", entry_obj_unmap },
+    /* filemap */
+    { "map", entry_obj_filemap },
+    { "unmap", entry_obj_filemap },
     { "files", entry_obj_files },
+    { "imagefiles", entry_obj_imagefiles },
+    { "activate", entry_obj_activate },
+    { "deactivate", entry_obj_filemap },
+    /* dep map */
+    { "dependents", entry_obj_dependents },
+    { "dependencies", entry_obj_dependencies },
+    { "depends", entry_obj_depends },
     { NULL, NULL }
 };
 

Added: trunk/base/src/registry2.0/portimage.tcl
===================================================================
--- trunk/base/src/registry2.0/portimage.tcl	                        (rev 0)
+++ trunk/base/src/registry2.0/portimage.tcl	2007-08-18 15:59:59 UTC (rev 28029)
@@ -0,0 +1,445 @@
+# et:ts=4
+# portimage.tcl
+# $Id$
+#
+# Copyright (c) 2004 Will Barton <wbb4 at opendarwin.org>
+# Copyright (c) 2002 Apple Computer, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of Apple Computer, Inc. nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+package provide portimage 2.0
+
+package require macports 1.0
+package require registry2 2.0
+package require registry_util 2.0
+package require Pextlib 1.0
+
+set UI_PREFIX "--> "
+
+#
+# Port Images are basically just installations of the destroot of a port into
+# ${macports::registry.path}/software/${name}/${version}_${revision}${variants}
+# They allow the user to instal multiple versions of the same port, treating
+# each revision and each different combination of variants as a "version".
+#
+# From there, the user can "activate" a port image.  This creates {sym,hard}links for
+# all files in the image into the ${prefix}.  Directories are created.
+# Activation checks the registry's file_map for any files which conflict with
+# other "active" ports, and will not overwrite the links to the those files.
+# The conflicting port must be deactivated first.
+#
+# The user can also "deactivate" an active port.  This will remove all {sym,hard}links
+# from ${prefix}, and if any directories are empty, remove them as well.  It
+# will also remove all of the references of the files from the registry's
+# file_map
+#
+# Compacting and Uncompacting of port images to save space will be implemented
+# at some point.
+#
+# For the creating and removing of links during activation and deactivation,
+# code very similar to what is used in portinstall is used.
+#
+
+namespace eval portimage {
+
+# Activate a "Port Image"
+proc activate {name specifier optionslist} {
+    global macports::prefix macports::registry.path UI_PREFIX
+    array set options $optionslist
+
+    if {[info exists options(ports_force)] && [string is true $options(ports_force)] } {
+        set force 1
+    } else {
+        set force 0
+    }
+
+    ui_msg "$UI_PREFIX [format [msgcat::mc "Activating %s %s"] $name $specifier]"
+
+    registry::read {
+
+        set requested [_check_registry $name $specifier]
+        set version [$requested version]
+        set revision [$requested revision]
+        set variants [$requested variants]
+        set specifier "${version}_$revision$variants"
+
+        set current [registry::entry installed $name]
+        if { [llength $current] > 1 } {
+            foreach i $current {
+                set iname [$i name]
+                set iversion [$i version]
+                set irevision [$i revision]
+                set ivariants [$i variants]
+                set ispecifier "${iversion}_$irevision$ivariants"
+                if { ![string equal $specifier $ispecifier]
+                        && [string equal [$i state] "installed"] } {
+                    return -code error "Image error: Another version of this port ($iname @${iversion}_${irevision}${ivariants}) is already active."
+                }
+            }
+        }
+
+        # this shouldn't be possible
+        if { ![string equal [$requested installtype] "image"] } {
+            return -code error "Image error: ${name} @${version}_${revision}${variants} not installed as an image."
+        }
+
+        if { [string equal [$requested state] "active" } {
+            return -code error "Image error: ${name} @${version}_${revision}${variants} is already active."
+        }
+
+        # compaction is not yet supported
+        #if { [$requested compact] != 0 } {
+        #    return -code error "Image error: ${name} @${version}_${revision}${variants} is compacted."
+        #}
+    }
+
+    _activate_contents $port $force
+    $requested state active
+}
+
+proc deactivate {name spec optionslist} {
+    global UI_PREFIX
+    array set options $optionslist
+
+    if {[info exists options(ports_force)] && [string is true $options(ports_force)] } {
+        set force 1
+    } else {
+        set force 0
+    }
+
+    ui_msg "$UI_PREFIX [format [msgcat::mc "Deactivating %s %s"] $name $v]"
+
+    if { [string equal $name {}] } {
+        throw registry::image-error "Registry error: Please specify the name of the port."
+    }
+    set ilist [registry::entry installed $name]
+    if { [llength $ilist] == 1 } {
+        set requested [lindex $ilist 0]
+    } else {
+        throw registry::image-error "Image error: port ${name} is not active."
+    }
+    set version [$requested version]
+    set revision [$requested revision]
+    set variants [$requested variants]
+    set specifier ${version}_$revision$variants
+
+    if { ![string equal $spec {}] && ![string equal $spec $specifier] } {
+        return -code error "Active version of $name is not $spec but $specifier."
+    }
+    if { ![string equal [$requested installtype] "image"] } {
+        return -code error "Image error: ${name} @${specifier} not installed as an image."
+    }
+    # this shouldn't be possible
+    if { [$requested state] != "installed" } {
+        return -code error "Image error: ${name} @${specifier} is not active."
+    }
+
+    # compaction not yet supported
+    #if { [registry::property_retrieve $ref compact] != 0 } {
+    #    return -code error "Image error: ${name} @${specifier} is compacted."
+    #}
+
+    registry::check_dependents $port $force
+
+    set imagedir [$requested imagedir]
+    set imagefiles [$requested files]
+
+    _deactivate_contents $requested $force
+    $requested state imaged
+}
+
+proc compact {name v} {
+    global UI_PREFIX
+
+    throw registry::image-error "Image error: compact/uncompact not yet implemented."
+}
+
+proc uncompact {name v} {
+    global UI_PREFIX
+
+    throw registry::image-error "Image error: compact/uncompact not yet implemented."
+}
+
+proc _check_registry {name specifier} {
+    global UI_PREFIX
+
+    if { [registry::decode_spec $specifier version revision variants] } {
+        set ilist [registry::entry imaged $name $version $revision $variants]
+        set valid 1
+    } else {
+        set valid [string equal $specifier {}]
+        set ilist [registry::entry imaged $name]
+    }
+
+    if { [llength $ilist] > 1 || (!$valid && [llength $ilist] == 1) } {
+        ui_msg "$UI_PREFIX [msgcat::mc "The following versions of $name are currently installed:"]"
+        foreach i $ilist {
+            set iname [$i name]
+            set iversion [$i version]
+            set irevision [$i revision]
+            set ivariants [$i variants]
+            if { [$i state] == "installed" } {
+                ui_msg "$UI_PREFIX [format [msgcat::mc "    %s @%s_%s%s (active)"] $iname $iversion $irevision $ivariants]"
+            } else {
+                ui_msg "$UI_PREFIX [format [msgcat::mc "    %s @%s_%s%s"] $iname $iversion $irevision $ivariants]"
+            }
+        }
+        if { $valid } {
+            throw registry::invalid "Registry error: Please specify the full version as recorded in the port registry."
+        } else {
+            throw registry::invalid "Registry error: Invalid version specified. Please specify a version as recorded in the port registry."
+        }
+    } else if { [llength $ilist] == 1 } {
+        return [lindex $ilist 0]
+    }
+    throw registry::invalid "Registry error: No port of $name installed."
+}
+
+## Activates a file from an image into the filesystem. Deals with symlinks,
+## directories and files.
+##
+## @param [in] srcfile path to file in image
+## @param [in] dstfile path to activate file to
+proc _activate_file {srcfile dstfile} {
+    switch { [file type $srcfile] } {
+        case link {
+            ui_debug "activating link: $dstfile"
+            file copy -force $srcfile $dstfile
+        }
+        case directory {
+            # Don't recursively copy directories
+            ui_debug "activating directory: $dstfile"
+            # Don't do anything if the directory already exists.
+            if { ![file isdirectory $dstfile] } {
+                file mkdir $dstfile
+                # copy attributes, set mtime and atime
+                eval file attributes [list $dstfile] [file attributes $srcfile]
+                file mtime $dstfile [file mtime $srcfile]
+                file atime $dstfile [file atime $srcfile]
+            }
+        }
+        case file {
+            ui_debug "activating file: $dstfile"
+            # Try a hard link first and if that fails, a symlink
+            try {
+                compat filelinkhard $dstfile $srcfile
+            } catch {*} {
+                ui_debug "hardlinking $srcfile to $dstfile failed; symlinking instead"
+                compat filelinksymbolic $dstfile $srcfile
+            }
+        }
+        default {
+            # don't activate e.g. a unix socket
+            ui_warning "skipped file $srcfile of unknown type [file type $srcfile]"
+        }
+    }
+}
+
+## Activates the contents of a port
+proc _activate_contents {port force} {
+    global macports::prefix
+
+    set files [list]
+    set imagedir [$port imagedir]
+    set imagefiles [$port imagefiles]
+
+    # first, ensure all files exist in the image dir
+    foreach file $imagefiles {
+        set srcfile $imagedir$file
+        # To be able to install links, we test if we can lstat the file to
+        # figure out if the source file exists (file exists will return
+        # false for symlinks on files that do not exist)
+        try {
+            file lstat $srcfile dummystatvar
+        } catch {*} {
+            throw registry::image-error "Image error: Source file $srcfile does not appear to exist (cannot lstat it).  Unable to activate port [$port name]."
+        }
+    }
+
+    set baksuffix .mp_[clock seconds]
+    set backups [list]
+
+    # This is big and hairy and probably could be done better.
+
+    # Then we remove the $imagedir from the path of the file in the contents
+    #  list  and check to see if that file exists
+    # Last, if the file exists, and belongs to another port, and force is set
+    #  we remove the file from the file_map, take ownership of it, and
+    #  clobber it
+    try {
+        registry::write {
+            foreach file $imagefiles {
+                set srcfile ${imagedir}${file}
+
+                set owner [registry::entry owner $file]
+
+                if { [string is true $force] } {
+                    # if we're forcing the activation, then we move any existing
+                    # files to a backup file, both in the filesystem and in the
+                    # registry
+                    if { [file exists $file] } {
+                        ui_warn "File $file already exists.  Moving to: $bakfile."
+                        file rename -force $file $file$baksuffix
+                        lappend backups $file
+                    }
+                    if { $owner != {} } {
+                        $owner deactivate [list $file]
+                        $owner activate [list $file] [list $file$baksuffix]
+                    }
+                } else {
+                    # if we're not forcing the activation, then we bail out if
+                    # we find any files that already exist, or have entries in
+                    # the registry
+                    if { $owner != {} && $owner != $port } {
+                        throw registry::image-error "Image error: $file is being used by the active [$owner name] port.  Please deactivate this port first, or use the -f flag to force the activation."
+                    } elseif { $owner == {} && [file exists $file] } {
+                        throw registry::image-error "Image error: $file already exists and does not belong to a registered port.  Unable to activate port [$owner name]."
+                    }
+                }
+
+                # Split out the filename's subpaths and add them to the
+                # imagefile list.
+                # We need directories first to make sure they will be there
+                # before links. However, because file mkdir creates all parent
+                # directories, we don't need to have them sorted from root to
+                # subpaths. We do need, nevertheless, all sub paths to make sure
+                # we'll set the directory attributes properly for all
+                # directories.
+                set directory [file dirname $file]
+                while { [lsearch -exact $files $directory] == -1 } {
+                    lappend files $directory
+                    set directory [file dirname $directory]
+                }
+
+                # Also add the filename to the imagefile list.
+                lappend files $file
+            }
+
+            # Sort the list in forward order, removing duplicates.
+            # Since the list is sorted in forward order, we're sure that
+            # directories are before their elements.
+            # We don't have to do this as mentioned above, but it makes the
+            # debug output of activate make more sense.
+            set theList [lsort -increasing -unique $files]
+
+            # Activate it, and catch errors so we can roll-back
+            try {
+                [$port activate $imagefiles]
+                foreach file $theList {
+                    _activate_file $imagedir$file $file
+                }
+            } catch {*} {
+                ui_debug "Activation failed, rolling back."
+                _deactivate_contents $port yes
+                throw
+            }
+        }
+    } catch {*} {
+        # if any errors occurred, move backed-up files back to their original
+        # locations, then rethrow the error. Transaction rollback will take care
+        # of this in the registry.
+        foreach file $backups {
+            file rename -force $file$baksuffix $file
+        }
+        throw
+    }
+}
+
+proc _deactivate_file {dstfile} {
+    switch { [file type $dstfile] } {
+        case link {
+            ui_debug "deactivating link: $dstfile"
+            file delete -- $dstfile
+        }
+        case directory {
+            # 0 item means empty.
+            if { [llength [readdir $dstfile]] == 0 } {
+                ui_debug "deactivating directory: $dstfile"
+                file delete -- $dstfile
+            } else {
+                ui_debug "$dstfile is not empty"
+            }
+        }
+        case file {
+            ui_debug "deactivating file: $dstfile"
+            file delete -- $dstfile
+        }
+        default {
+            # don't deactivate e.g. a unix socket
+            ui_warning "skipped file $dstfile of unknown type [file type $dstfile]"
+        }
+    }
+}
+
+proc _deactivate_contents {port force} {
+
+    set files [list]
+
+    set realfiles [$port files]
+
+    foreach file $realfiles {
+        set owner [registry::entry owner $file]
+        if { [file exists $file] || (![catch {file type $file}] && [file type $file] == "link") } {
+            # Normalize the file path to avoid removing the intermediate
+            # symlinks (remove the empty directories instead)
+            # Remark: paths in the registry may be not normalized.
+            # This is not really a problem and it is in fact preferable.
+            # Indeed, if I change the activate code to include normalized paths
+            # instead of the paths we currently have, users' registry won't
+            # match and activate will say that some file exists but doesn't
+            # belong to any port.
+            set theFile [compat filenormalize $file]
+            lappend files $theFile
+
+            # Split out the filename's subpaths and add them to the image list
+            # as well.
+            set directory [file dirname $theFile]
+            while { [lsearch -exact $files $directory] == -1 } {
+                lappend files $directory
+                set directory [file dirname $directory]
+            }
+        } else {
+            ui_debug "$file does not exist."
+        }
+    }
+
+    # Sort the list in reverse order, removing duplicates.
+    # Since the list is sorted in reverse order, we're sure that directories
+    # are after their elements.
+    set theList [lsort -decreasing -unique $files]
+
+    registry::write {
+        # Remove all elements.
+        $port deactivate $realfiles
+        foreach file $theList {
+            _deactivate_file $file
+        }
+    }
+}
+
+# End of portimage namespace
+}

Added: trunk/base/src/registry2.0/portuninstall.tcl
===================================================================
--- trunk/base/src/registry2.0/portuninstall.tcl	                        (rev 0)
+++ trunk/base/src/registry2.0/portuninstall.tcl	2007-08-18 15:59:59 UTC (rev 28029)
@@ -0,0 +1,190 @@
+# et:ts=4
+# portuninstall.tcl
+# $Id$
+#
+# Copyright (c) 2002 - 2003 Apple Computer, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of Apple Computer, Inc. nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+package provide portuninstall 2.0
+
+package require registry2 2.0
+package require registry_util 2.0
+
+set UI_PREFIX "---> "
+
+namespace eval portuninstall {
+
+proc uninstall {portname {specifier ""} optionslist} {
+	global uninstall.force uninstall.nochecksum UI_PREFIX
+	array set options $optionslist
+
+	if {[info exists options(ports_force)] && [string is true $options(ports_force)] } {
+        set force 1
+    } else {
+        set force 0
+    }
+
+    if { [registry::decode_spec $specifier version revision variants] } {
+        set ilist [registry::entry imaged $portname $version $revision $variants]
+        set valid 1
+    } else {
+        set valid [string equal $specifier {}]
+        set ilist [registry::entry imaged $portname]
+    }
+
+	if { [llength $ilist] > 1 } {
+		ui_msg "$UI_PREFIX [format [msgcat::mc "The following versions of %s are currently installed:"] $portname]"
+		foreach i $ilist { 
+			set iname [lindex $i 0]
+			set iactive [lindex $i 4]
+            set ispec "[$i version]_[$i revision][$i variants]"
+			if { [string equal [$i state] installed] } {
+				ui_msg "$UI_PREFIX [format [msgcat::mc "	%s @%s (active)"] $iname $ispec]"
+			} elseif { $iactive == 1 } {
+				ui_msg "$UI_PREFIX [format [msgcat::mc "	%s @%s"] $iname $ispec]"
+			}
+		}
+        if { $valid } {
+            throw registry::invalid "Registry error: Please specify the full version as recorded in the port registry."
+        } else {
+            throw registry::invalid "Registry error: Invalid version specified. Please specify a version as recorded in the port registry."
+        }
+	} elseif { [llength $ilist] == 1 } {
+        set port [lindex $ilist 0]
+	} else {
+        throw registry::invalid "Registry error: $portname not registered as installed"
+    }
+
+    if { [string equal [$port installtype] direct] } {
+        # if port is installed directly, check its dependents
+        registry::check_dependents $port $force
+    } else {
+        # if it's an image, deactivate it (and check dependents there)
+        if { [string equal [$port state] installed] } {
+            portimage::deactivate $portname ${version}_${revision}${variants} $optionslist
+        }
+	}
+
+	ui_msg "$UI_PREFIX [format [msgcat::mc "Uninstalling %s %s_%s%s"] $portname $version $revision $variants]"
+
+    # pkg_uninstall isn't used anywhere as far as I can tell and I intend to add
+    # some proper pre-/post- hooks to uninstall/deactivate.
+
+	# Look to see if the port has registered an uninstall procedure
+	#set uninstall [registry::property_retrieve $ref pkg_uninstall] 
+	#if { $uninstall != 0 } {
+	#	if {![catch {eval $uninstall} err]} {
+	#		pkg_uninstall $portname ${version}_${revision}${variants}
+	#	} else {
+	#		global errorInfo
+	#		ui_debug "$errorInfo"
+	#		ui_error [format [msgcat::mc "Could not evaluate pkg_uninstall procedure: %s"] $err]
+	#	}
+	#}
+
+	set contents [$port files]
+
+    set bak_suffix .mp_[time seconds]
+    set uninst_err 0
+    set files [list]
+    foreach file $contents {
+        if { !([info exists uninstall.nochecksum]
+                && [string is true $uninstall.nochecksum]) } {
+            set sum1 [$port md5sum $file]
+            if {![catch {set sum2 [md5 $file]}] && ![string match $sum1 $sum2]}{
+                ui_info "$UI_PREFIX  [format [msgcat::mc "Original checksum does not match for %s, saving a copy to %s"] $file $file$bak_suffix]"
+                file copy $file $file$bak_suffix
+            }
+        }
+
+        # Normalize the file path to avoid removing the intermediate
+        # symlinks (remove the empty directories instead)
+        set theFile [compat filenormalize $file
+        lappend files $theFile
+
+        # Split out the filename's subpaths and add them to the
+        # list as well.
+        set directory [file dirname $theFile]
+        while { [lsearch -exact $files $directory] == -1 } { 
+            lappend files $directory
+            set directory [file dirname $directory]
+        }
+    }
+
+    # Sort the list in reverse order, removing duplicates.
+    # Since the list is sorted in reverse order, we're sure that directories
+    # are after their elements.
+    set theList [lsort -decreasing -unique $files]
+
+    # Remove all elements.
+    foreach file $theList {
+        _uninstall_file $file
+    }
+
+    ui_info "$UI_PREFIX [format [msgcat::mc "Uninstall is removing %s from the port registry."] $portname]"
+    registry::entry delete $port
+    return 0
+}
+
+proc _uninstall_file {dstfile} {
+	if { ![catch {set type [file type $dstfile]}] } {
+        switch {$type} {
+            case link {
+                ui_debug "uninstalling link: $dstfile"
+                file delete -- $dstfile
+            }
+            case directory {
+                # 0 item means empty.
+                if { [llength [readdir $dstfile]] == 0 } {
+                    ui_debug "uninstalling directory: $dstfile"
+                    file delete -- $dstfile
+                } else {
+                    ui_debug "$dstfile is not empty"
+                }
+            }
+            case file {
+                ui_debug "uninstalling file: $dstfile"
+                file delete -- $dstfile
+            }
+            default {
+                ui_debug "skip file of unknown type $type: $dstfile"
+            }
+        }
+	} else {
+		ui_debug "skip missing file: $dstfile"
+	}
+}
+
+proc _uninstall_list {filelist} {
+	foreach file $filelist {
+		_uninstall_file $file
+	}
+}
+
+# End of portuninstall namespace
+}

Added: trunk/base/src/registry2.0/registry_util.tcl
===================================================================
--- trunk/base/src/registry2.0/registry_util.tcl	                        (rev 0)
+++ trunk/base/src/registry2.0/registry_util.tcl	2007-08-18 15:59:59 UTC (rev 28029)
@@ -0,0 +1,79 @@
+# et:ts=4
+# registry_util.tcl
+# $Id$
+#
+# Copyright (c) 2007 Chris Pickel
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of Apple Computer, Inc. nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+package provide registry_util 2.0
+
+package require registry2 2.0
+
+namespace eval registry {
+
+## Decodes a version specifier into its component values. Values will be
+## returned into the variables named by `version`, `revision`, and `variants`,
+## with `revision` and `variants` possibly being set to the empty string if none
+## were specified.
+##
+## This accept a full specifier such as `1.2.1_3+cool-lame` (to disable MP3
+## support) or a bare version, such as `1.2.1`. If a revision is not specified,
+## then the returned revision and variants will be empty.
+##
+## @param [in] specifier a specifier, as described above
+## @param [out] version  name of a variable to return version into
+## @param [out] revision name of a variable to return revision into
+## @param [out] variants name of a variable to return variants into
+## @return               true if `specifier` is a valid specifier, else false
+proc decode_spec {specifier version revision variants} {
+    upvar 1 $version ver $revision rev $variants var
+    return [regexp {^([^-+_]+)(_([^-+]+)(([-+][^-+]+)*))?$} - ver - rev var]
+}
+
+## Checks that the given port has no dependent ports. If it does, throws an
+## error if force wasn't set (emits a message if it is).
+##
+## @param [in] port  a registry::entry to check
+## @param [in] force if true, continue even if there are dependents
+proc check_dependents {port force} {
+    # Check and make sure no ports depend on this one
+    set deplist [$port dependents]
+    if { [llength $deplist] > 0 } {
+        ui_msg "$UI_PREFIX [format [msgcat::mc "Unable to uninstall %s %s_%s%s, the following ports depend on it:"] $portname $version $revision $variants]"
+        foreach depport $deplist {
+            ui_msg "$UI_PREFIX [format [msgcat::mc "	%s"] $depport]"
+        }
+        if { [string is true $force] } {
+            ui_warn "Uninstall forced.  Proceeding despite dependencies."
+        } else {
+            throw registry::uninstall-error "Please uninstall the ports that depend on $portname first."
+        }
+    }
+}
+
+}

Modified: trunk/base/src/registry2.0/tests/common.tcl
===================================================================
--- trunk/base/src/registry2.0/tests/common.tcl	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/src/registry2.0/tests/common.tcl	2007-08-18 15:59:59 UTC (rev 28029)
@@ -34,6 +34,23 @@
             }"
 }
 
+proc test_set {statement value} {
+    uplevel 1 "\
+        puts -nonewline {checking if $statement is \[list $value\]... }
+        if {\[catch {
+                set actual \[lsort $statement\]
+                if {\$actual == \[lsort \[subst {\[list $value\]}\]\]} { \n\
+                    puts yes
+                } else { \n\
+                    puts \"no (was \$actual)\" \n\
+                    exit 1 \n\
+                } \n\
+            } msg\]} { \n\
+                puts \"caught error: \$msg\" \n\
+                exit 1 \n\
+            }"
+}
+
 proc test_throws {statement error} {
     uplevel 1 "\
         puts -nonewline {checking if \[$statement\] throws $error... }

Added: trunk/base/src/registry2.0/tests/depends.tcl
===================================================================
--- trunk/base/src/registry2.0/tests/depends.tcl	                        (rev 0)
+++ trunk/base/src/registry2.0/tests/depends.tcl	2007-08-18 15:59:59 UTC (rev 28029)
@@ -0,0 +1,163 @@
+# Test file for registry::entry dependencies
+# Syntax:
+# tclsh depends.tcl <Pextlib name>
+
+proc main {pextlibname} {
+    load $pextlibname
+
+    # totally lame that file delete won't do it
+	eval exec rm -f [glob -nocomplain test.db*]
+
+    registry::open test.db
+
+    # some really contrived ports to test with
+    # this is the dependency graph, roughly:
+
+    #            a1     a2     a3
+    #              \   /      /
+    #              b1&b2     /
+    #              /\ /\    c
+    #             f  d  g  /
+    #                 \   /
+    #                   e
+
+    registry::write {
+        set a1 [registry::entry create a 1 0 0 {}]
+        set a2 [registry::entry create a 2 0 0 {}]
+        set a3 [registry::entry create a 3 0 0 {}]
+        $a1 depends b
+        $a2 depends b
+        $a3 depends c
+
+        set b1 [registry::entry create b 1 0 0 {}]
+        set b2 [registry::entry create b 2 0 0 {}]
+        $b1 depends d
+        $b1 depends f
+        $b2 depends d
+        $b2 depends g
+
+        set c [registry::entry create c 1 0 0 {}]
+        $c depends e
+
+        set d [registry::entry create d 1 0 0 {}]
+        $d depends e
+
+        set e [registry::entry create e 1 0 0 {}]
+        set f [registry::entry create f 1 0 0 {}]
+        set g [registry::entry create g 1 0 0 {}]
+
+        $a1 state installed
+        $a2 state imaged
+        $a3 state imaged
+        $b1 state installed
+        $b2 state imaged
+        $c state installed
+        $d state installed
+        $e state installed
+        $f state installed
+        $g state imaged
+    }
+
+    registry::read {
+        test_set {[$a1 dependents]} {}
+        test_set {[$a2 dependents]} {}
+        test_set {[$a3 dependents]} {}
+        test_set {[$b1 dependents]} {$a1}
+        test_set {[$b2 dependents]} {}
+        test_set {[$c dependents]} {}
+        test_set {[$d dependents]} {$b1}
+        test_set {[$e dependents]} {$c $d}
+        test_set {[$f dependents]} {$b1}
+        test_set {[$g dependents]} {}
+
+        test_set {[$a1 dependencies]} {$b1}
+        test_set {[$b1 dependencies]} {$d $f}
+        test_set {[$c dependencies]} {$e}
+        test_set {[$d dependencies]} {$e}
+        test_set {[$e dependencies]} {}
+        test_set {[$f dependencies]} {}
+        test_set {[$g dependencies]} {}
+    }
+
+    registry::write {
+        $b1 state imaged
+        $b2 state installed
+        $g state installed
+    }
+
+    registry::read {
+        test_set {[$b1 dependents]} {}
+        test_set {[$b2 dependents]} {$a1}
+        test_set {[$c dependents]} {}
+        test_set {[$d dependents]} {$b2}
+        test_set {[$e dependents]} {$c $d}
+        test_set {[$f dependents]} {}
+        test_set {[$g dependents]} {$b2}
+
+        test_set {[$a1 dependencies]} {$b2}
+        test_set {[$b2 dependencies]} {$d $g}
+        test_set {[$c dependencies]} {$e}
+        test_set {[$d dependencies]} {$e}
+    }
+
+    registry::write {
+        $a1 state imaged
+        $a2 state installed
+        $f state imaged
+    }
+
+    registry::read {
+        test_set {[$b1 dependents]} {}
+        test_set {[$b2 dependents]} {$a2}
+        test_set {[$c dependents]} {}
+        test_set {[$d dependents]} {$b2}
+        test_set {[$e dependents]} {$c $d}
+        test_set {[$f dependents]} {}
+        test_set {[$g dependents]} {$b2}
+
+        test_set {[$a2 dependencies]} {$b2}
+        test_set {[$b2 dependencies]} {$d $g}
+        test_set {[$c dependencies]} {$e}
+        test_set {[$d dependencies]} {$e}
+    }
+
+    registry::write {
+        $a2 state imaged
+        $a3 state installed
+    }
+
+    registry::read {
+        test_set {[$b1 dependents]} {}
+        test_set {[$b2 dependents]} {}
+        test_set {[$c dependents]} {$a3}
+        test_set {[$d dependents]} {$b2}
+        test_set {[$e dependents]} {$c $d}
+        test_set {[$f dependents]} {}
+        test_set {[$g dependents]} {$b2}
+
+        test_set {[$a3 dependencies]} {$c}
+        test_set {[$b2 dependencies]} {$d $g}
+        test_set {[$c dependencies]} {$e}
+        test_set {[$d dependencies]} {$e}
+    }
+
+    registry::write {
+        $b2 state imaged
+        $d state imaged
+    }
+
+    registry::read {
+        test_set {[$c dependents]} {$a3}
+        test_set {[$d dependents]} {}
+        test_set {[$e dependents]} {$c}
+        test_set {[$g dependents]} {}
+
+        test_set {[$a3 dependencies]} {$c}
+        test_set {[$d dependencies]} {$e}
+    }
+
+    file delete test.db
+}
+
+source tests/common.tcl
+main $argv

Modified: trunk/base/src/registry2.0/tests/entry.tcl
===================================================================
--- trunk/base/src/registry2.0/tests/entry.tcl	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/src/registry2.0/tests/entry.tcl	2007-08-18 15:59:59 UTC (rev 28029)
@@ -1,16 +1,17 @@
 # $Id$
-# Test file for registry::item
+# Test file for registry::entry
 # Syntax:
-# tclsh item.tcl <Pextlib name>
+# tclsh entry.tcl <Pextlib name>
 
 proc main {pextlibname} {
     load $pextlibname
 
-	file delete [glob -nocomplain test.db*]
+    # totally lame that file delete won't do it
+	eval exec rm -f [glob -nocomplain test.db*]
 
     # can't create registry in some brain-dead place or in protected place
     test_throws {registry::open /some/brain/dead/place} registry::cannot-init
-    test_throws {registry::open /etc/macports_test~} registry::cannot-init
+    test_throws {registry::open /etc/macports_test_prot~} registry::cannot-init
 
     # can't use registry before it's opened
     test_throws {registry::write {}} registry::misuse
@@ -25,11 +26,11 @@
     registry::write {
 
         # create some (somewhat contrived) ports to play with
-        set vim1 [registry::entry create vim 7.1.000 0 {multibyte +} 0]
+        set vim1 [registry::entry create vim 7.1.000 0 +cscope+multibyte 0]
         set vim2 [registry::entry create vim 7.1.002 0 {} 0]
-        set vim3 [registry::entry create vim 7.1.002 0 {multibyte +} 0]
+        set vim3 [registry::entry create vim 7.1.002 0 +cscope+multibyte 0]
         set zlib [registry::entry create zlib 1.2.3 1 {} 0]
-        set pcre [registry::entry create pcre 7.1 1 {utf8 +} 0]
+        set pcre [registry::entry create pcre 7.1 1 +utf8 0]
 
         # check that their properties can be set
         $vim1 state imaged
@@ -38,6 +39,12 @@
         $zlib state installed
         $pcre state imaged
 
+        $vim1 installtype image
+        $vim2 installtype image
+        $vim3 installtype image
+        $zlib installtype direct
+        $pcre installtype image
+
     }
 
     # check that their properties can be retrieved
@@ -47,59 +54,90 @@
         test_equal {[$vim2 epoch]} 0
         test_equal {[$vim3 version]} 7.1.002
         test_equal {[$zlib revision]} 1
-        test_equal {[$pcre variants]} {utf8 +}
+        test_equal {[$pcre variants]} +utf8
     
         # check that imaged and installed give correct results
-        # have to sort these because their orders aren't defined
-        set imaged [registry::entry imaged]
-        test_equal {[lsort $imaged]} {[lsort "$vim1 $vim2 $vim3 $zlib $pcre"]}
+        test_set {[registry::entry imaged]} {$vim1 $vim2 $vim3 $zlib $pcre}
+        test_set {[registry::entry installed]} {$vim3 $zlib}
+        test_set {[registry::entry imaged vim]} {$vim1 $vim2 $vim3}
+        test_set {[registry::entry imaged vim 7.1.000]} {$vim1}
+        test_set {[registry::entry imaged vim 7.1.002]} {$vim2 $vim3}
+        test_set {[registry::entry imaged vim 7.1.002 0 {}]} {$vim2}
+        test_set {[registry::entry imaged vim 7.1.002 0 +cscope+multibyte]} \
+            {$vim3}
 
-        set installed [registry::entry installed]
-        test_equal {[lsort $installed]} {[lsort "$vim3 $zlib"]}
+        # try searching for ports
+        # note that 7.1.2 != 7.1.002 but the VERSION collation should be smart
+        # enough to ignore the zeroes
+        test_set {[registry::entry search name vim version 7.1.2]} {$vim2 $vim3}
+        test_set {[registry::entry search variants {}]} {$vim2 $zlib}
+        test_set {[registry::entry search -glob name vi*]} {$vim1 $vim2 $vim3}
+        test_set {[registry::entry search -regexp name {zlib|pcre}]} \
+            {$zlib $pcre}
     }
 
+    # try mapping files and checking their owners
+    registry::write {
 
-    # try searching for ports
-    # note that 7.1.2 != 7.1.002 but the VERSION collation should be smart
-    # enough to ignore the zeroes
-    set vim71002 [registry::entry search name vim version 7.1.2]
-    test_equal {[lsort $vim71002]} {[lsort "$vim2 $vim3"]}
+        test_equal {[registry::entry owner /opt/local/bin/vimtutor]} {}
+        test_equal {[$vim3 files]} {}
 
-    set no_variants [registry::entry search variants {}]
-    test_equal {[lsort $no_variants]} {[lsort "$vim2 $zlib"]}
+        $vim1 map {}
+        $vim1 map /opt/local/bin/vim
+        $vim1 map [list /opt/local/bin/vimdiff /opt/local/bin/vimtutor]
+        $vim2 map [$vim1 imagefiles]
+        $vim3 map [$vim1 imagefiles]
+        test_equal {[registry::entry owner /opt/local/bin/vimtutor]} {}
+        test_equal {[$vim3 files]} {}
 
-    set vistar [registry::entry search -glob name vi*]
-    test_equal {[lsort $vistar]} {[lsort "$vim1 $vim2 $vim3"]}
-
-    set zlibpcre [registry::entry search -regexp name {zlib|pcre}]
-    test_equal {[lsort $zlibpcre]} {[lsort "$zlib $pcre"]}
-
-    # try mapping files and checking their owners
-    registry::write {
-        $vim3 map [list /opt/local/bin/vim]
-        $vim3 map [list /opt/local/bin/vimdiff /opt/local/bin/vimtutor]
+        $vim3 activate [$vim3 imagefiles]
         test_equal {[registry::entry owner /opt/local/bin/vimtutor]} {$vim3}
         test_equal {[registry::entry owner /opt/local/bin/emacs]} {}
 
-        # don't have to sort because order is defined as alpha
-        test_equal {[$vim3 files]} {[list /opt/local/bin/vim \
-            /opt/local/bin/vimdiff /opt/local/bin/vimtutor]}
-        test_equal {[$zlib files]} {[list]}
+        test_set {[$vim3 imagefiles]} {/opt/local/bin/vim \
+            /opt/local/bin/vimdiff /opt/local/bin/vimtutor}
+        test_set {[$vim3 files]} [$vim3 imagefiles]
+        test_set {[$zlib imagefiles]} {}
 
+        # try activating over files
+        test_throws {$vim2 activate [$vim2 imagefiles]} registry::already-active
+
         # try unmapping and remapping
-        $vim3 unmap {/opt/local/bin/vim}
+        $vim3 unmap /opt/local/bin/vimtutor
+        test_equal {[registry::entry owner /opt/local/bin/vimtutor]} {}
+
+        $vim3 deactivate /opt/local/bin/vim
         test_equal {[registry::entry owner /opt/local/bin/vim]} {}
-        $vim3 map {/opt/local/bin/vim}
+        $vim3 unmap /opt/local/bin/vim
+        test_equal {[registry::entry owner /opt/local/bin/vim]} {}
+        $vim3 map /opt/local/bin/vim
+        test_equal {[registry::entry owner /opt/local/bin/vim]} {}
+        $vim3 activate /opt/local/bin/vim
+        puts [$vim3 files]
+        puts [registry::entry owner /opt/local/bin/vim]
         test_equal {[registry::entry owner /opt/local/bin/vim]} {$vim3}
 
+        # activate to a different location
+        $vim3 deactivate /opt/local/bin/vimdiff
+        $vim3 activate /opt/local/bin/vimdiff /opt/local/bin/vimdiff.0
+        $vim2 activate /opt/local/bin/vimdiff
+        test_set {[$vim3 files]} {/opt/local/bin/vim /opt/local/bin/vimdiff.0}
+        test_set {[$vim3 imagefiles]} {/opt/local/bin/vim \
+            /opt/local/bin/vimdiff}
+        test_equal {[registry::entry owner /opt/local/bin/vimdiff]} {$vim2}
+        test_equal {[registry::entry owner /opt/local/bin/vimdiff.0]} {$vim3}
+
         # make sure you can't unmap a file you don't own
         test_throws {$zlib unmap [list /opt/local/bin/vim]} registry::invalid
         test_throws {$zlib unmap [list /opt/local/bin/emacs]} registry::invalid
     }
 
+    test_set {[$vim3 imagefiles]} {/opt/local/bin/vim /opt/local/bin/vimdiff}
+    test_set {[$vim3 files]} {/opt/local/bin/vim /opt/local/bin/vimdiff.0}
+
     # try some deletions
-    test_equal {[registry::entry installed zlib]} {$zlib}
-    test_equal {[registry::entry imaged pcre]} {$pcre}
+    test_set {[registry::entry installed zlib]} {$zlib}
+    test_set {[registry::entry imaged pcre]} {$pcre}
 
     # try rolling a deletion back
     registry::write {
@@ -110,7 +148,7 @@
 
     # try actually deleting something
     registry::entry delete $pcre
-    test_throws {registry::entry open pcre 7.1 1 {utf8 +} 0} \
+    test_throws {registry::entry open pcre 7.1 1 +utf8 0} \
         registry::not-found
     test {![registry::entry exists $pcre]}
 
@@ -135,12 +173,12 @@
     test {[registry::entry exists $zlib]}
 
     # check that pcre is still gone
-    test_throws {registry::entry open pcre 7.1 1 {utf8 +} 0} \
+    test_throws {registry::entry open pcre 7.1 1 +utf8 0} \
         registry::not-found
 
     registry::close
 
-	file delete [glob -nocomplain test.db*]
+    file delete test.db
 }
 
 source tests/common.tcl

Modified: trunk/base/src/registry2.0/util.c
===================================================================
--- trunk/base/src/registry2.0/util.c	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/src/registry2.0/util.c	2007-08-18 15:59:59 UTC (rev 28029)
@@ -35,6 +35,7 @@
 #include <tcl.h>
 
 #include "util.h"
+#include "entryobj.h"
 
 /**
  * Generates a unique proc name starting with prefix.
@@ -158,6 +159,28 @@
 }
 
 /**
+ * Sets a given name to be an entry object.
+ *
+ * @param [in] interp  Tcl interpreter to create the entry within
+ * @param [in] name    name to associate the given entry with
+ * @param [in] entry   entry to associate with the given name
+ * @param [out] errPtr description of error if it couldn't be set
+ * @return             true if success; false if failure
+ * @see set_object
+ */
+int set_entry(Tcl_Interp* interp, char* name, reg_entry* entry,
+        reg_error* errPtr) {
+    if (set_object(interp, name, entry, "entry", entry_obj_cmd, NULL,
+                errPtr)) {
+        int size = strlen(name) + 1;
+        entry->proc = malloc(size*sizeof(char));
+        memcpy(entry->proc, name, size);
+        return 1;
+    }
+    return 0;
+}
+
+/**
  * Reports a sqlite3 error to Tcl.
  *
  * Queries the database for the most recent error message and sets it as the
@@ -213,6 +236,15 @@
     return TCL_ERROR;
 }
 
+const char* string_or_null(Tcl_Obj* obj) {
+    const char* string = Tcl_GetString(obj);
+    if (string[0] == '\0') {
+        return NULL;
+    } else {
+        return string;
+    }
+}
+
 int recast(void* userdata, cast_function* fn, free_function* del, void*** outv,
         void** inv, int inc, reg_error* errPtr) {
     void** result = malloc(inc*sizeof(void*));
@@ -232,13 +264,33 @@
     return 1;
 }
 
+int entry_to_obj(Tcl_Interp* interp, Tcl_Obj** obj, reg_entry* entry,
+        reg_error* errPtr) {
+    if (entry->proc == NULL) {
+        char* name = unique_name(interp, "registry::entry");
+        if (!set_entry(interp, name, entry, errPtr)) {
+            free(name);
+            return 0;
+        }
+        free(name);
+    }
+    *obj = Tcl_NewStringObj(entry->proc, -1);
+    return 1;
+}
+
+int list_entry_to_obj(Tcl_Interp* interp, Tcl_Obj*** objs,
+        reg_entry** entries, int entry_count, reg_error* errPtr) {
+    return recast(interp, (cast_function*)entry_to_obj, NULL, (void***)objs,
+            (void**)entries, entry_count, errPtr);
+}
+
 static int obj_to_string(void* userdata UNUSED, char** string, Tcl_Obj* obj,
         reg_error* errPtr UNUSED) {
     *string = Tcl_GetString(obj);
     return 1;
 }
 
-int list_obj_to_string(char*** strings, const Tcl_Obj** objv, int objc,
+int list_obj_to_string(char*** strings, Tcl_Obj** objv, int objc,
         reg_error* errPtr) {
     return recast(NULL, (cast_function*)obj_to_string, NULL, (void***)strings,
             (void**)objv, objc, errPtr);
@@ -254,7 +306,7 @@
     Tcl_DecrRefCount(obj);
 }
 
-int list_string_to_obj(Tcl_Obj*** objv, const char** strings, int objc,
+int list_string_to_obj(Tcl_Obj*** objv, char** strings, int objc,
         reg_error* errPtr) {
     return recast(NULL, (cast_function*)string_to_obj, (free_function*)free_obj,
             (void***)objv, (void**)strings, objc, errPtr);

Modified: trunk/base/src/registry2.0/util.h
===================================================================
--- trunk/base/src/registry2.0/util.h	2007-08-18 15:08:33 UTC (rev 28028)
+++ trunk/base/src/registry2.0/util.h	2007-08-18 15:59:59 UTC (rev 28029)
@@ -36,6 +36,7 @@
 #include <sqlite3.h>
 
 #include <cregistry/registry.h>
+#include <cregistry/entry.h>
 
 typedef struct {
     char* option;
@@ -53,6 +54,8 @@
         Tcl_ObjCmdProc* proc, reg_error* errPtr);
 int set_object(Tcl_Interp* interp, char* name, void* value, char* type,
         Tcl_ObjCmdProc* proc, Tcl_CmdDeleteProc* deleteProc, reg_error* errPtr);
+int set_entry(Tcl_Interp* interp, char* name, reg_entry* entry,
+        reg_error* errPtr);
 
 void set_sqlite_result(Tcl_Interp* interp, sqlite3* db, const char* query);
 
@@ -61,14 +64,21 @@
 int all_objects(Tcl_Interp* interp, sqlite3* db, char* query, char* prefix,
         set_object_function* setter);
 
+const char* string_or_null(Tcl_Obj* obj);
+
 int recast(void* userdata, cast_function* fn, free_function* del, void*** outv,
         void** inv, int inc, reg_error* errPtr);
 
+int entry_to_obj(Tcl_Interp* interp, Tcl_Obj** obj, reg_entry* entry,
+        reg_error* errPtr);
+int list_entry_to_obj(Tcl_Interp* interp, Tcl_Obj*** objs,
+        reg_entry** entries, int entry_count, reg_error* errPtr);
+
 void free_strings(void* userdata UNUSED, char** strings, int count);
 
-int list_obj_to_string(char*** strings, const Tcl_Obj** objv, int objc,
+int list_obj_to_string(char*** strings, Tcl_Obj** objv, int objc,
         reg_error* errPtr);
-int list_string_to_obj(Tcl_Obj*** objv, const char** strings, int objc,
+int list_string_to_obj(Tcl_Obj*** objv, char** strings, int objc,
         reg_error* errPtr);
 
 #endif /* _UTIL_H */

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/macports-changes/attachments/20070818/1b3e68fb/attachment.html


More information about the macports-changes mailing list