static IdeVcsConfig * ide_git_vcs_get_config (IdeVcs *vcs) { g_return_val_if_fail (IDE_IS_GIT_VCS (vcs), NULL); return (IdeVcsConfig *)ide_git_vcs_config_new (); }
static void ide_git_vcs_reload_worker (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { IdeGitVcs *self = source_object; g_autoptr(GgitRepository) repository1 = NULL; g_autoptr(GgitRepository) repository2 = NULL; GError *error = NULL; g_assert (G_IS_TASK (task)); g_assert (IDE_IS_GIT_VCS (self)); g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); if (!(repository1 = ide_git_vcs_load (self, &error)) || !(repository2 = ide_git_vcs_load (self, &error))) { g_task_return_error (task, error); return; } g_set_object (&self->repository, repository1); g_set_object (&self->change_monitor_repository, repository2); if (!ide_git_vcs_load_monitor (self, &error)) { g_task_return_error (task, error); return; } g_task_return_boolean (task, TRUE); }
static gboolean ide_git_vcs_load_monitor (IdeGitVcs *self, GError **error) { gboolean ret = TRUE; g_assert (IDE_IS_GIT_VCS (self)); if (self->monitor == NULL) { g_autoptr(GFile) location = NULL; g_autoptr(GFileMonitor) monitor = NULL; g_autoptr(GFile) heads_dir = NULL; GFileMonitorFlags flags = G_FILE_MONITOR_WATCH_MOUNTS; location = ggit_repository_get_location (self->repository); heads_dir = g_file_get_child (location, "refs/heads"); monitor = g_file_monitor (heads_dir, flags, NULL, error); ret = !!monitor; if (monitor) { IDE_TRACE_MSG ("Git index monitor registered."); g_signal_connect_object (monitor, "changed", G_CALLBACK (ide_git_vcs__monitor_changed_cb), self, G_CONNECT_SWAPPED); self->monitor = g_object_ref (monitor); } } return ret; }
static GgitRepository * ide_git_vcs_load (IdeGitVcs *self, GError **error) { g_autoptr(GFile) location = NULL; GgitRepository *repository = NULL; IdeContext *context; GFile *project_file; g_assert (IDE_IS_GIT_VCS (self)); context = ide_object_get_context (IDE_OBJECT (self)); project_file = ide_context_get_project_file (context); if (!(location = ggit_repository_discover (project_file, error))) return NULL; if (!(repository = ggit_repository_open (location, error))) return NULL; /* Only set once, on load. Not thread-safe otherwise. */ if (self->working_directory == NULL) self->working_directory = ggit_repository_get_workdir (repository); return repository; }
static void ide_git_vcs__monitor_changed_cb (IdeGitVcs *self, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { g_autofree gchar *name = NULL; g_autofree gchar *other_name = NULL; IDE_ENTRY; g_assert (IDE_IS_GIT_VCS (self)); g_assert (G_IS_FILE (file)); g_assert (!other_file || G_IS_FILE (other_file)); name = g_file_get_basename (file); if (other_file != NULL) other_name = g_file_get_basename (other_file); if (dzl_str_equal0 (name, "index") || dzl_str_equal0 (other_name, "index")) { dzl_clear_source (&self->changed_timeout); self->changed_timeout = g_timeout_add_seconds (DEFAULT_CHANGED_TIMEOUT_SECS, ide_git_vcs__changed_timeout_cb, self); } IDE_EXIT; }
/** * ide_git_vcs_get_repository: * * Retrieves the underlying #GgitRepository used by @vcs. * * Returns: (transfer none): A #GgitRepository. */ GgitRepository * ide_git_vcs_get_repository (IdeGitVcs *self) { g_return_val_if_fail (IDE_IS_GIT_VCS (self), NULL); return self->repository; }
static gchar * ide_git_vcs_get_branch_name (IdeVcs *vcs) { IdeGitVcs *self = (IdeGitVcs *)vcs; GgitRef *ref; gchar *ret = NULL; g_assert (IDE_IS_GIT_VCS (self)); g_mutex_lock (&self->repository_mutex); ref = ggit_repository_get_head (self->repository, NULL); g_mutex_unlock (&self->repository_mutex); if (ref != NULL) { ret = g_strdup (ggit_ref_get_shorthand (ref)); g_object_unref (ref); } else { /* initial commit, no branch name yet */ ret = g_strdup ("master"); } return ret; }
static void ide_git_vcs_list_status_worker (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { ListStatus *state = task_data; g_autoptr(GListStore) store = NULL; g_autoptr(GFile) workdir = NULL; g_autoptr(GgitRepository) repository = NULL; g_autoptr(GgitStatusOptions) options = NULL; g_autoptr(GString) pathspec = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *relative = NULL; gchar *strv[] = { NULL, NULL }; g_assert (G_IS_TASK (task)); g_assert (IDE_IS_GIT_VCS (source_object)); g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); g_assert (state != NULL); g_assert (G_IS_FILE (state->repository_location)); if (!(repository = ggit_repository_open (state->repository_location, &error))) { g_task_return_error (task, g_steal_pointer (&error)); return; } if (!(workdir = ggit_repository_get_workdir (repository))) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to locate working directory"); return; } g_set_object (&state->workdir, workdir); if (state->directory_or_file != NULL) relative = g_file_get_relative_path (workdir, state->directory_or_file); strv[0] = relative; options = ggit_status_options_new (GGIT_STATUS_OPTION_DEFAULT, GGIT_STATUS_SHOW_INDEX_AND_WORKDIR, (const gchar **)strv); store = g_list_store_new (IDE_TYPE_VCS_FILE_INFO); g_set_object (&state->store, store); if (!ggit_repository_file_status_foreach (repository, options, ide_git_vcs_list_status_cb, state, &error)) g_task_return_error (task, g_steal_pointer (&error)); else g_task_return_pointer (task, g_steal_pointer (&store), g_object_unref); }
static GFile * ide_git_vcs_get_working_directory (IdeVcs *vcs) { IdeGitVcs *self = (IdeGitVcs *)vcs; g_return_val_if_fail (IDE_IS_GIT_VCS (self), NULL); return self->working_directory; }
static void ide_git_vcs_real_reloaded (IdeGitVcs *self, GgitRepository *repository) { g_assert (IDE_IS_GIT_VCS (self)); g_assert (GGIT_IS_REPOSITORY (repository)); g_object_notify (G_OBJECT (self), "branch-name"); }
static GListModel * ide_git_vcs_list_status_finish (IdeVcs *vcs, GAsyncResult *result, GError **error) { g_return_val_if_fail (IDE_IS_GIT_VCS (vcs), NULL); g_return_val_if_fail (G_IS_TASK (result), NULL); return g_task_propagate_pointer (G_TASK (result), error); }
static gboolean ide_git_vcs__changed_timeout_cb (gpointer user_data) { IdeGitVcs *self = user_data; IDE_ENTRY; g_assert (IDE_IS_GIT_VCS (self)); self->changed_timeout = 0; ide_git_vcs_reload_async (self, NULL, NULL, NULL); IDE_RETURN (G_SOURCE_REMOVE); }
static void handle_reload_from_changed_timeout (GObject *object, GAsyncResult *result, gpointer user_data) { IdeGitVcs *self = (IdeGitVcs *)object; g_autoptr(GError) error = NULL; g_assert (IDE_IS_GIT_VCS (self)); g_assert (G_IS_ASYNC_RESULT (result)); /* Call finish() so that "changed" is emitted */ if (!ide_git_vcs_reload_finish (self, result, &error)) g_warning ("%s", error->message); }
static GFile * ide_git_vcs_get_working_directory (IdeVcs *vcs) { IdeGitVcs *self = (IdeGitVcs *)vcs; g_return_val_if_fail (IDE_IS_GIT_VCS (self), NULL); /* Note: This function is expected to be thread-safe for * those holding a reference to @vcs. So * @working_directory cannot be changed after creation * and must be valid for the lifetime of @vcs. */ return self->working_directory; }
static void ide_git_vcs_init_async__reload_cb (GObject *object, GAsyncResult *result, gpointer user_data) { IdeGitVcs *self = (IdeGitVcs *)object; g_autoptr(GTask) task = user_data; g_autoptr(GError) error = NULL; g_assert (G_IS_TASK (task)); g_assert (IDE_IS_GIT_VCS (self)); if (!ide_git_vcs_reload_finish (self, result, &error)) g_task_return_error (task, g_steal_pointer (&error)); else g_task_return_boolean (task, TRUE); }
static IdeBufferChangeMonitor * ide_git_vcs_get_buffer_change_monitor (IdeVcs *vcs, IdeBuffer *buffer) { IdeGitVcs *self = (IdeGitVcs *)vcs; IdeContext *context; g_return_val_if_fail (IDE_IS_GIT_VCS (vcs), NULL); context = ide_object_get_context (IDE_OBJECT (vcs)); return g_object_new (IDE_TYPE_GIT_BUFFER_CHANGE_MONITOR, "buffer", buffer, "context", context, "repository", self->change_monitor_repository, NULL); }
static void ide_git_vcs_init_async (GAsyncInitable *initable, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { IdeGitVcs *self = (IdeGitVcs *)initable; g_autoptr(GTask) task = NULL; g_return_if_fail (IDE_IS_GIT_VCS (self)); task = g_task_new (self, cancellable, callback, user_data); ide_git_vcs_reload_async (self, cancellable, ide_git_vcs_init_async__reload_cb, g_object_ref (task)); }
static gboolean ide_git_vcs_reload_finish (IdeGitVcs *self, GAsyncResult *result, GError **error) { GTask *task = (GTask *)result; gboolean ret; IDE_ENTRY; g_return_val_if_fail (IDE_IS_GIT_VCS (self), FALSE); self->reloading = FALSE; g_signal_emit (self, gSignals [RELOADED], 0, self->change_monitor_repository); ret = g_task_propagate_boolean (task, error); IDE_RETURN (ret); }
static void ide_git_vcs_reload_async (IdeGitVcs *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autoptr(GTask) task = NULL; IDE_ENTRY; g_assert (IDE_IS_GIT_VCS (self)); g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); task = g_task_new (self, cancellable, callback, user_data); g_task_run_in_thread (task, ide_git_vcs_reload_worker); IDE_EXIT; }
static void ide_git_buffer_change_monitor__vcs_reloaded_cb (IdeGitBufferChangeMonitor *self, GgitRepository *new_repository, IdeGitVcs *vcs) { IDE_ENTRY; g_assert (IDE_IS_GIT_BUFFER_CHANGE_MONITOR (self)); g_assert (IDE_IS_GIT_VCS (vcs)); g_set_object (&self->repository, new_repository); /* force reload of the git object on next calculation */ g_clear_object (&self->cached_blob); ide_git_buffer_change_monitor_recalculate (self); IDE_EXIT; }
static void ide_git_vcs_list_status_async (IdeVcs *vcs, GFile *directory_or_file, gboolean include_descendants, gint io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { IdeGitVcs *self = (IdeGitVcs *)vcs; g_autoptr(GTask) task = NULL; ListStatus *state; IDE_ENTRY; g_return_if_fail (IDE_IS_GIT_VCS (self)); g_return_if_fail (!directory_or_file || G_IS_FILE (directory_or_file)); g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); g_mutex_lock (&self->repository_mutex); state = g_slice_new0 (ListStatus); state->directory_or_file = g_object_ref (directory_or_file); state->repository_location = ggit_repository_get_location (self->repository); state->recursive = !!include_descendants; g_mutex_unlock (&self->repository_mutex); task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, ide_git_vcs_list_status_async); g_task_set_priority (task, io_priority); g_task_set_return_on_cancel (task, TRUE); g_task_set_task_data (task, state, list_status_free); if (state->repository_location == NULL) g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "No repository loaded"); else g_task_run_in_thread (task, ide_git_vcs_list_status_worker); IDE_EXIT; }
static void ide_git_vcs__monitor_changed_cb (IdeGitVcs *self, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { IDE_ENTRY; g_assert (IDE_IS_GIT_VCS (self)); if (self->changed_timeout != 0) g_source_remove (self->changed_timeout); self->changed_timeout = g_timeout_add_seconds (DEFAULT_CHANGED_TIMEOUT_SECS, ide_git_vcs__changed_timeout_cb, self); IDE_EXIT; }
static gboolean ide_git_vcs_load_monitor_locked (IdeGitVcs *self, GError **error) { gboolean ret = TRUE; g_assert (IDE_IS_GIT_VCS (self)); if (self->monitor == NULL) { g_autoptr(GFile) location = NULL; g_autoptr(GFileMonitor) monitor = NULL; GError *local_error = NULL; location = ggit_repository_get_location (self->repository); monitor = g_file_monitor_directory (location, G_FILE_MONITOR_NONE, NULL, &local_error); if (monitor == NULL) { g_warning ("Failed to establish git monitor: %s", local_error->message); g_propagate_error (error, local_error); ret = FALSE; } else { IDE_TRACE_MSG ("Git index monitor registered."); g_signal_connect_object (monitor, "changed", G_CALLBACK (ide_git_vcs__monitor_changed_cb), self, G_CONNECT_SWAPPED); self->monitor = g_steal_pointer (&monitor); } } return ret; }
static gboolean ide_git_vcs_is_ignored (IdeVcs *vcs, GFile *file, GError **error) { g_autofree gchar *name = NULL; IdeGitVcs *self = (IdeGitVcs *)vcs; gboolean ret = FALSE; g_assert (IDE_IS_GIT_VCS (self)); g_assert (G_IS_FILE (file)); name = g_file_get_relative_path (self->working_directory, file); if (g_strcmp0 (name, ".git") == 0) return TRUE; if (name != NULL) return ggit_repository_path_is_ignored (self->repository, name, error); return ret; }
static GgitRepository * ide_git_vcs_load (IdeGitVcs *self, GError **error) { g_autofree gchar *uri = NULL; g_autoptr(GFile) location = NULL; GgitRepository *repository = NULL; IdeContext *context; GFile *project_file; g_assert (IDE_IS_GIT_VCS (self)); g_assert (error != NULL); context = ide_object_get_context (IDE_OBJECT (self)); project_file = ide_context_get_project_file (context); if (!(location = ide_git_vcs_discover (self, project_file, error))) { if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) return NULL; g_clear_error (error); /* Fallback to libgit2(-glib) discovery */ if (!(location = ggit_repository_discover (project_file, error))) return NULL; } uri = g_file_get_uri (location); g_debug ("Discovered .git location at ā%sā", uri); if (!(repository = ggit_repository_open (location, error))) return NULL; /* Note: Only set this once on load, otherwise not thread-safe. */ if (self->working_directory == NULL) self->working_directory = ggit_repository_get_workdir (repository); return repository; }
static void ide_git_vcs_reload_worker (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { IdeGitVcs *self = source_object; g_autoptr(GgitRepository) repository1 = NULL; g_autoptr(GgitRepository) repository2 = NULL; g_autoptr(GError) error = NULL; IDE_ENTRY; g_assert (G_IS_TASK (task)); g_assert (IDE_IS_GIT_VCS (self)); g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); if (!(repository1 = ide_git_vcs_load (self, &error)) || !(repository2 = ide_git_vcs_load (self, &error))) { g_debug ("%s", error->message); g_task_return_error (task, g_steal_pointer (&error)); IDE_EXIT; } g_mutex_lock (&self->repository_mutex); g_set_object (&self->repository, repository1); g_set_object (&self->change_monitor_repository, repository2); if (!ide_git_vcs_load_monitor_locked (self, &error)) g_task_return_error (task, g_steal_pointer (&error)); else g_task_return_boolean (task, TRUE); g_mutex_unlock (&self->repository_mutex); IDE_EXIT; }
static gboolean ide_git_vcs_is_ignored (IdeVcs *vcs, GFile *file, GError **error) { g_autofree gchar *name = NULL; IdeGitVcs *self = (IdeGitVcs *)vcs; gboolean ret = FALSE; g_assert (IDE_IS_GIT_VCS (self)); g_assert (G_IS_FILE (file)); /* Note: this function is required to be thread-safe so that workers * can check if files are ignored from a thread without * round-tripping to the main thread. */ /* self->working_directory is not changed after creation, so safe * to access it from a thread. */ name = g_file_get_relative_path (self->working_directory, file); if (g_strcmp0 (name, ".git") == 0) return TRUE; /* * If we have a valid name to work with, we want to query the * repository. But this could be called from a thread, so ensure * we are the only thread accessing self->repository right now. */ if (name != NULL) { g_mutex_lock (&self->repository_mutex); ret = ggit_repository_path_is_ignored (self->repository, name, error); g_mutex_unlock (&self->repository_mutex); } return ret; }
/* * This is like ggit_repository_discover() but seems to work inside * of the flatpak runtime. Something is preventing git_repository_discover() * in libgit2 from working correctly, possibly the mount setup. Either * way, until it's fixed we can use this. */ static GFile * ide_git_vcs_discover (IdeGitVcs *self, GFile *file, GError **error) { g_autofree gchar *name = NULL; g_autoptr(GFile) parent = NULL; g_autoptr(GFile) git = NULL; g_autoptr(GFile) child = NULL; GFile *ret; g_assert (IDE_IS_GIT_VCS (self)); g_assert (G_IS_FILE (file)); /* * TODO: Switch to using the new discover_full(). */ if (!g_file_is_native (file)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Only native file systems are supported for git."); return NULL; } name = g_file_get_basename (file); if (g_strcmp0 (name, ".git") == 0) return g_object_ref (file); /* * Work around for in-tree tests which we do not * want to use the git backend. * * TODO: Allow options during context creation. */ child = g_file_get_child (file, ".you-dont-git-me"); if (g_file_query_exists (child, NULL)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "The project has blocked use of the git plugin"); return NULL; } g_clear_object (&child); child = g_file_get_child (file, ".git"); if (g_file_query_exists (child, NULL)) return g_object_ref (child); parent = g_file_get_parent (file); if (parent == NULL || g_file_equal (parent, file)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "Failed to discover git directory"); return NULL; } ret = ide_git_vcs_discover (self, parent, error); return ret; }