[gnome-builder/wip/chergert/git-oop: 21/34] add discover API and RPCs
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/git-oop: 21/34] add discover API and RPCs
- Date: Sat, 23 Mar 2019 00:11:25 +0000 (UTC)
commit 6ec36d5a041f8ca6a96bc3b56b1e425da32cb9c9
Author: Christian Hergert <chergert redhat com>
Date: Thu Mar 21 13:12:02 2019 -0700
add discover API and RPCs
src/plugins/git/gbp-git-client.c | 136 +++++++++++++++++++++++++
src/plugins/git/gbp-git-client.h | 197 +++++++++++++++++++-----------------
src/plugins/git/gbp-git.c | 171 +++++++++++++++++++++++++++++++
src/plugins/git/gbp-git.h | 11 ++
src/plugins/git/gnome-builder-git.c | 79 ++++++++++++++-
5 files changed, 498 insertions(+), 96 deletions(-)
---
diff --git a/src/plugins/git/gbp-git-client.c b/src/plugins/git/gbp-git-client.c
index 0ae5857f4..e0a4d1852 100644
--- a/src/plugins/git/gbp-git-client.c
+++ b/src/plugins/git/gbp-git-client.c
@@ -1022,3 +1022,139 @@ gbp_git_client_create_repo_finish (GbpGitClient *self,
return ide_task_propagate_boolean (IDE_TASK (result), error);
}
+
+typedef struct
+{
+ GFile *workdir;
+ gchar *branch;
+ guint is_worktree : 1;
+} Discover;
+
+static void
+discover_free (Discover *state)
+{
+ g_clear_object (&state->workdir);
+ g_clear_pointer (&state->branch, g_free);
+ g_slice_free (Discover, state);
+}
+
+static void
+gbp_git_client_discover_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GbpGitClient *self = (GbpGitClient *)object;
+ g_autoptr(IdeTask) task = user_data;
+ g_autoptr(GVariant) reply = NULL;
+ g_autoptr(GError) error = NULL;
+ const gchar *workdir = NULL;
+ const gchar *branch = NULL;
+ Discover *state;
+ gboolean is_worktree = FALSE;
+ gboolean r;
+
+ g_assert (GBP_IS_GIT_CLIENT (self));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_TASK (task));
+
+ if (!gbp_git_client_call_finish (self, result, &reply, &error))
+ {
+ ide_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ state = ide_task_get_task_data (task);
+
+ r = JSONRPC_MESSAGE_PARSE (reply,
+ "workdir", JSONRPC_MESSAGE_GET_STRING (&workdir),
+ "branch", JSONRPC_MESSAGE_GET_STRING (&branch),
+ "is-worktree", JSONRPC_MESSAGE_GET_BOOLEAN (&is_worktree)
+ );
+
+ if (!r)
+ {
+ ide_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "Invalid reply from peer");
+ return;
+ }
+
+ state->workdir = g_file_new_for_uri (workdir);
+ state->branch = g_strdup (branch);
+ state->is_worktree = !!is_worktree;
+
+ ide_task_return_boolean (task, TRUE);
+}
+
+void
+gbp_git_client_discover_async (GbpGitClient *self,
+ GFile *directory,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(IdeTask) task = NULL;
+ g_autoptr(GVariant) command = NULL;
+ g_autofree gchar *uri = NULL;
+ Discover *state;
+
+ g_return_if_fail (GBP_IS_GIT_CLIENT (self));
+ g_return_if_fail (G_IS_FILE (directory));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ state = g_slice_new0 (Discover);
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, gbp_git_client_discover_async);
+ ide_task_set_task_data (task, state, discover_free);
+
+ uri = g_file_get_uri (directory);
+
+ command = JSONRPC_MESSAGE_NEW (
+ "location", JSONRPC_MESSAGE_PUT_STRING (uri)
+ );
+
+ gbp_git_client_call_async (self,
+ "git/discover",
+ command,
+ cancellable,
+ gbp_git_client_discover_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+gbp_git_client_discover_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GFile **workdir,
+ gchar **branch,
+ gboolean *is_worktree,
+ GError **error)
+{
+ g_return_val_if_fail (GBP_IS_GIT_CLIENT (self), FALSE);
+ g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
+
+ ide_clear_param (workdir, NULL);
+ ide_clear_param (branch, NULL);
+ ide_clear_param (is_worktree, FALSE);
+
+ if (ide_task_propagate_boolean (IDE_TASK (result), error))
+ {
+ Discover *state = ide_task_get_task_data (IDE_TASK (result));
+
+ g_assert (state != NULL);
+
+ if (workdir != NULL)
+ *workdir = g_steal_pointer (&state->workdir);
+
+ if (branch != NULL)
+ *branch = g_steal_pointer (&state->branch);
+
+ if (is_worktree != NULL)
+ *is_worktree = state->is_worktree;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/src/plugins/git/gbp-git-client.h b/src/plugins/git/gbp-git-client.h
index 493a6ca70..18d699c0f 100644
--- a/src/plugins/git/gbp-git-client.h
+++ b/src/plugins/git/gbp-git-client.h
@@ -35,98 +35,109 @@ typedef enum
G_DECLARE_FINAL_TYPE (GbpGitClient, gbp_git_client, GBP, GIT_CLIENT, IdeObject)
-GbpGitClient *gbp_git_client_from_context (IdeContext *context);
-void gbp_git_client_call_async (GbpGitClient *self,
- const gchar *method,
- GVariant *params,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean gbp_git_client_call_finish (GbpGitClient *self,
- GAsyncResult *result,
- GVariant **reply,
- GError **error);
-void gbp_git_client_is_ignored_async (GbpGitClient *self,
- const gchar *path,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean gbp_git_client_is_ignored_finish (GbpGitClient *self,
- GAsyncResult *result,
- GError **error);
-void gbp_git_client_list_status_async (GbpGitClient *self,
- const gchar *directory_or_file,
- gboolean include_descendants,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-GPtrArray *gbp_git_client_list_status_finish (GbpGitClient *self,
- GAsyncResult *result,
- GError **error);
-void gbp_git_client_list_refs_by_kind_async (GbpGitClient *self,
- GbpGitRefKind kind,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-GPtrArray *gbp_git_client_list_refs_by_kind_finish (GbpGitClient *self,
- GAsyncResult *result,
- GError **error);
-void gbp_git_client_switch_branch_async (GbpGitClient *self,
- const gchar *branch_name,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean gbp_git_client_switch_branch_finish (GbpGitClient *self,
- GAsyncResult *result,
- gchar **switch_to_directory,
- GError **error);
-void gbp_git_client_create_repo_async (GbpGitClient *self,
- GFile *in_directory,
- gboolean bare,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean gbp_git_client_create_repo_finish (GbpGitClient *self,
- GAsyncResult *result,
- GError **error);
-void gbp_git_client_clone_url_async (GbpGitClient *self,
- const gchar *url,
- GFile *destination,
- const gchar *branch,
- IdeNotification *notif,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean gbp_git_client_clone_url_finish (GbpGitClient *self,
- GAsyncResult *result,
- GError **error);
-void gbp_git_client_update_submodules_async (GbpGitClient *self,
- IdeNotification *notif,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean gbp_git_client_update_submodules_finish (GbpGitClient *self,
- GAsyncResult *result,
- GError **error);
-GVariant *gbp_git_client_read_config (GbpGitClient *self,
- const gchar *key,
- GCancellable *cancellable,
- GError **error);
-gboolean gbp_git_client_update_config (GbpGitClient *self,
- gboolean global,
- const gchar *key,
- GVariant *value,
- GCancellable *cancellable,
- GError **error);
-void gbp_git_client_update_config_async (GbpGitClient *self,
- gboolean global,
- const gchar *key,
- GVariant *value,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean gbp_git_client_update_config_finish (GbpGitClient *self,
- GAsyncResult *result,
- GError **error);
+GbpGitClient *gbp_git_client_from_context (IdeContext *context);
+void gbp_git_client_call_async (GbpGitClient *self,
+ const gchar *method,
+ GVariant *params,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_client_call_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GVariant **reply,
+ GError **error);
+void gbp_git_client_is_ignored_async (GbpGitClient *self,
+ const gchar *path,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_client_is_ignored_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GError **error);
+void gbp_git_client_list_status_async (GbpGitClient *self,
+ const gchar *directory_or_file,
+ gboolean include_descendants,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GPtrArray *gbp_git_client_list_status_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GError **error);
+void gbp_git_client_list_refs_by_kind_async (GbpGitClient *self,
+ GbpGitRefKind kind,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GPtrArray *gbp_git_client_list_refs_by_kind_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GError **error);
+void gbp_git_client_switch_branch_async (GbpGitClient *self,
+ const gchar *branch_name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_client_switch_branch_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ gchar **switch_to_directory,
+ GError **error);
+void gbp_git_client_create_repo_async (GbpGitClient *self,
+ GFile *in_directory,
+ gboolean bare,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_client_create_repo_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GError **error);
+void gbp_git_client_clone_url_async (GbpGitClient *self,
+ const gchar *url,
+ GFile *destination,
+ const gchar *branch,
+ IdeNotification *notif,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_client_clone_url_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GError **error);
+void gbp_git_client_update_submodules_async (GbpGitClient *self,
+ IdeNotification *notif,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_client_update_submodules_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GError **error);
+GVariant *gbp_git_client_read_config (GbpGitClient *self,
+ const gchar *key,
+ GCancellable *cancellable,
+ GError **error);
+gboolean gbp_git_client_update_config (GbpGitClient *self,
+ gboolean global,
+ const gchar *key,
+ GVariant *value,
+ GCancellable *cancellable,
+ GError **error);
+void gbp_git_client_update_config_async (GbpGitClient *self,
+ gboolean global,
+ const gchar *key,
+ GVariant *value,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_client_update_config_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GError **error);
+void gbp_git_client_discover_async (GbpGitClient *self,
+ GFile *directory,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_client_discover_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GFile **workdir,
+ gchar **branch,
+ gboolean *is_worktree,
+ GError **error);
G_END_DECLS
diff --git a/src/plugins/git/gbp-git.c b/src/plugins/git/gbp-git.c
index 69b9ba89f..463029015 100644
--- a/src/plugins/git/gbp-git.c
+++ b/src/plugins/git/gbp-git.c
@@ -774,3 +774,174 @@ gbp_git_create_repo_finish (GbpGit *self,
return g_task_propagate_boolean (G_TASK (result), error);
}
+
+typedef struct
+{
+ GFile *directory;
+ GFile *workdir;
+ gchar *branch;
+ guint is_worktree : 1;
+} Discover;
+
+static void
+discover_free (Discover *state)
+{
+ g_clear_object (&state->directory);
+ g_clear_object (&state->workdir);
+ g_clear_pointer (&state->branch, g_free);
+ g_slice_free (Discover, state);
+}
+
+static void
+gbp_git_discover_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ Discover *state = task_data;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GFile) location = NULL;
+ g_autoptr(GFile) workdir = NULL;
+ g_autoptr(GgitRepository) repository = NULL;
+ g_autofree gchar *worktree_branch = NULL;
+ gboolean is_worktree = FALSE;
+
+ g_assert (G_IS_TASK (task));
+ g_assert (GBP_IS_GIT (source_object));
+ g_assert (state != NULL);
+ g_assert (G_IS_FILE (state->directory));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ /* Short-circuit if we don't .git */
+ if (!(location = ggit_repository_discover_full (state->directory, TRUE, NULL, &error)))
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ "Failed to locate git repository location");
+ return;
+ }
+
+ /* If @location is a regular file, we might have a git-worktree link */
+ if (g_file_query_file_type (location, 0, NULL) == G_FILE_TYPE_REGULAR)
+ {
+ g_autofree gchar *contents = NULL;
+ gsize len;
+
+ if (g_file_load_contents (location, NULL, &contents, &len, NULL, NULL))
+ {
+ /* g_file_load_contents() provides a suffix \0 */
+ g_auto(GStrv) lines = g_strsplit (contents, "\n", 0);
+
+ for (guint i = 0; lines[i] != NULL; i++)
+ {
+ gchar *line = lines[i];
+
+ if (g_str_has_prefix (line, "gitdir: "))
+ {
+ g_autoptr(GFile) location_parent = g_file_get_parent (location);
+ const gchar *path = line + strlen ("gitdir: ");
+ const gchar *branch;
+
+ is_worktree = TRUE;
+
+ g_clear_object (&location);
+
+ if (g_path_is_absolute (path))
+ location = g_file_new_for_path (path);
+ else
+ location = g_file_resolve_relative_path (location_parent, path);
+
+ /*
+ * Worktrees only have a single branch, and it is the name
+ * of the suffix of .git/worktrees/<name>
+ */
+ if ((branch = strrchr (line, G_DIR_SEPARATOR)))
+ worktree_branch = g_strdup (branch + 1);
+
+ break;
+ }
+ }
+ }
+ }
+
+ if (!(repository = ggit_repository_open (location, &error)))
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ workdir = ggit_repository_get_workdir (repository);
+
+ g_assert (G_IS_FILE (location));
+ g_assert (G_IS_FILE (workdir));
+ g_assert (GGIT_IS_REPOSITORY (repository));
+
+ if (worktree_branch == NULL)
+ {
+ g_autoptr(GgitRef) ref = NULL;
+
+ if ((ref = ggit_repository_get_head (repository, NULL)))
+ worktree_branch = g_strdup (ggit_ref_get_shorthand (ref));
+
+ if (worktree_branch == NULL)
+ worktree_branch = g_strdup ("master");
+ }
+
+ state->workdir = g_file_dup (workdir);
+ state->branch = g_steal_pointer (&worktree_branch);
+ state->is_worktree = !!is_worktree;
+
+ g_task_return_boolean (task, TRUE);
+}
+
+void
+gbp_git_discover_async (GbpGit *self,
+ GFile *directory,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+ Discover *state;
+
+ g_return_if_fail (GBP_IS_GIT (self));
+ g_return_if_fail (G_IS_FILE (directory));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ state = g_slice_new0 (Discover);
+ state->directory = g_file_dup (directory);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gbp_git_discover_async);
+ g_task_set_task_data (task, state, (GDestroyNotify)discover_free);
+ g_task_run_in_thread (task, gbp_git_discover_worker);
+}
+
+gboolean
+gbp_git_discover_finish (GbpGit *self,
+ GAsyncResult *result,
+ GFile **workdir,
+ gchar **branch,
+ gboolean *is_worktree,
+ GError **error)
+{
+ g_return_val_if_fail (GBP_IS_GIT (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+ g_return_val_if_fail (workdir != NULL, FALSE);
+ g_return_val_if_fail (branch != NULL, FALSE);
+ g_return_val_if_fail (is_worktree != NULL, FALSE);
+
+ if (g_task_propagate_boolean (G_TASK (result), error))
+ {
+ Discover *state = g_task_get_task_data (G_TASK (result));
+
+ *workdir = g_steal_pointer (&state->workdir);
+ *branch = g_steal_pointer (&state->branch);
+ *is_worktree = state->is_worktree;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/src/plugins/git/gbp-git.h b/src/plugins/git/gbp-git.h
index 1d9bad1c9..04120bd8b 100644
--- a/src/plugins/git/gbp-git.h
+++ b/src/plugins/git/gbp-git.h
@@ -134,5 +134,16 @@ void gbp_git_update_config_async (GbpGit *self,
gboolean gbp_git_update_config_finish (GbpGit *self,
GAsyncResult *result,
GError **error);
+void gbp_git_discover_async (GbpGit *self,
+ GFile *directory,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_discover_finish (GbpGit *self,
+ GAsyncResult *result,
+ GFile **workdir,
+ gchar **branch,
+ gboolean *is_worktree,
+ GError **error);
G_END_DECLS
diff --git a/src/plugins/git/gnome-builder-git.c b/src/plugins/git/gnome-builder-git.c
index 8c929a922..4e1a58b43 100644
--- a/src/plugins/git/gnome-builder-git.c
+++ b/src/plugins/git/gnome-builder-git.c
@@ -906,6 +906,78 @@ handle_create_repo (JsonrpcServer *server,
client_op_ref (op));
}
+/* Handle Discover {{{1 */
+
+static void
+handle_discover_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GbpGit *git = (GbpGit *)object;
+ g_autoptr(ClientOp) op = user_data;
+ g_autoptr(GVariant) reply = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GFile) workdir = NULL;
+ g_autofree gchar *branch = NULL;
+ g_autofree gchar *uri = NULL;
+ gboolean is_worktree = FALSE;
+
+ g_assert (GBP_IS_GIT (git));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (op != NULL);
+
+ if (!gbp_git_discover_finish (git, result, &workdir, &branch, &is_worktree, &error))
+ {
+ client_op_error (op, error);
+ return;
+ }
+
+ uri = g_file_get_uri (workdir);
+
+ reply = JSONRPC_MESSAGE_NEW (
+ "workdir", JSONRPC_MESSAGE_PUT_STRING (uri),
+ "branch", JSONRPC_MESSAGE_PUT_STRING (branch),
+ "is-worktree", JSONRPC_MESSAGE_PUT_BOOLEAN (is_worktree)
+ );
+
+ client_op_reply (op, reply);
+}
+
+static void
+handle_discover (JsonrpcServer *server,
+ JsonrpcClient *client,
+ const gchar *method,
+ GVariant *id,
+ GVariant *params,
+ GbpGit *git)
+{
+ g_autoptr(ClientOp) op = NULL;
+ g_autoptr(GFile) location = NULL;
+ const gchar *uri;
+
+ g_assert (JSONRPC_IS_SERVER (server));
+ g_assert (JSONRPC_IS_CLIENT (client));
+ g_assert (g_str_equal (method, "git/discover"));
+ g_assert (id != NULL);
+ g_assert (GBP_IS_GIT (git));
+
+ op = client_op_new (client, id);
+
+ if (!JSONRPC_MESSAGE_PARSE (params, "location", JSONRPC_MESSAGE_GET_STRING (&uri)))
+ {
+ client_op_bad_params (op);
+ return;
+ }
+
+ location = g_file_new_for_uri (uri);
+
+ gbp_git_discover_async (git,
+ location,
+ op->cancellable,
+ (GAsyncReadyCallback)handle_discover_cb,
+ client_op_ref (op));
+}
+
/* Main Loop and Setup {{{1 */
gint
@@ -946,16 +1018,17 @@ main (gint argc,
#define ADD_HANDLER(method, func) \
jsonrpc_server_add_handler (server, method, (JsonrpcServerHandler)func, g_object_ref (git), g_object_unref)
- ADD_HANDLER ("initialize", handle_initialize);
+ ADD_HANDLER ("$/cancelRequest", handle_cancel_request);
ADD_HANDLER ("git/cloneUrl", handle_clone_url);
ADD_HANDLER ("git/createRepo", handle_create_repo);
- ADD_HANDLER ("git/readConfig", handle_read_config);
+ ADD_HANDLER ("git/discover", handle_discover);
ADD_HANDLER ("git/isIgnored", handle_is_ignored);
ADD_HANDLER ("git/listRefsByKind", handle_list_refs_by_kind);
+ ADD_HANDLER ("git/readConfig", handle_read_config);
ADD_HANDLER ("git/switchBranch", handle_switch_branch);
ADD_HANDLER ("git/updateConfig", handle_update_config);
ADD_HANDLER ("git/updateSubmodules", handle_update_submodules);
- ADD_HANDLER ("$/cancelRequest", handle_cancel_request);
+ ADD_HANDLER ("initialize", handle_initialize);
#undef ADD_HANDLER
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]