changed the refresh hotkey
[giggle.git] / libgiggle / giggle-git.c
blob5e3bb42a25befe73bdcdb2d8d8f950d45bef64e2
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2007 Imendio AB
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
21 #include <config.h>
23 #include "giggle-dispatcher.h"
24 #include "giggle-git.h"
25 #include "giggle-remote.h"
27 #define d(x) x
29 typedef struct GiggleGitPriv GiggleGitPriv;
31 struct GiggleGitPriv {
32 GiggleDispatcher *dispatcher;
33 gchar *directory;
34 gchar *git_dir;
35 gchar *project_dir;
36 gchar *project_name;
37 gchar *description;
39 GList *remotes;
41 GHashTable *jobs;
44 typedef struct {
45 guint id;
46 GiggleJob *job;
47 GiggleJobDoneCallback callback;
48 gpointer user_data;
49 GDestroyNotify destroy_notify;
50 } GitJobData;
52 static void git_finalize (GObject *object);
53 static void git_get_property (GObject *object,
54 guint param_id,
55 GValue *value,
56 GParamSpec *pspec);
57 static void git_set_property (GObject *object,
58 guint param_id,
59 const GValue *value,
60 GParamSpec *pspec);
61 static gboolean git_verify_directory (const gchar *directory,
62 gchar **git_dir,
63 GError **error);
64 static void git_job_data_free (GitJobData *data);
65 static void git_execute_callback (GiggleDispatcher *dispatcher,
66 guint id,
67 GError *error,
68 const gchar *output_str,
69 gsize output_len,
70 GiggleGit *git);
71 static GQuark giggle_git_error_quark (void);
73 G_DEFINE_TYPE (GiggleGit, giggle_git, G_TYPE_OBJECT)
75 enum {
76 PROP_0,
77 PROP_DESCRIPTION,
78 PROP_DIRECTORY,
79 PROP_GIT_DIR,
80 PROP_PROJECT_DIR,
81 PROP_PROJECT_NAME,
82 PROP_REMOTES
85 enum {
86 CHANGED,
87 LAST_SIGNAL
90 static guint signals[LAST_SIGNAL] = { 0, };
92 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GIGGLE_TYPE_GIT, GiggleGitPriv))
94 static void
95 giggle_git_class_init (GiggleGitClass *class)
97 GObjectClass *object_class = G_OBJECT_CLASS (class);
99 object_class->finalize = git_finalize;
100 object_class->get_property = git_get_property;
101 object_class->set_property = git_set_property;
103 g_object_class_install_property (object_class,
104 PROP_DESCRIPTION,
105 g_param_spec_string ("description",
106 "Description",
107 "The project's description",
108 NULL,
109 G_PARAM_READABLE));
110 g_object_class_install_property (object_class,
111 PROP_DIRECTORY,
112 g_param_spec_string ("directory",
113 "Directory",
114 "the working directory",
115 NULL,
116 G_PARAM_READABLE));
117 g_object_class_install_property (object_class,
118 PROP_GIT_DIR,
119 g_param_spec_string ("git-dir",
120 "Git-Directory",
121 "The equivalent of $GIT_DIR",
122 NULL,
123 G_PARAM_READABLE));
124 g_object_class_install_property (object_class,
125 PROP_PROJECT_DIR,
126 g_param_spec_string ("project-dir",
127 "Project Directory",
128 "The location of the checkout currently being worked on",
129 NULL,
130 G_PARAM_READABLE));
131 g_object_class_install_property (object_class,
132 PROP_PROJECT_NAME,
133 g_param_spec_string ("project-name",
134 "Project Name",
135 "The name of the project (guessed)",
136 NULL,
137 G_PARAM_READABLE));
138 g_object_class_install_property (object_class,
139 PROP_REMOTES,
140 g_param_spec_string ("remotes",
141 "Remotes",
142 "The remote sources",
143 NULL, G_PARAM_READABLE));
145 signals[CHANGED] =
146 g_signal_new ("changed",
147 G_OBJECT_CLASS_TYPE (object_class),
148 G_SIGNAL_RUN_LAST,
149 G_STRUCT_OFFSET (GiggleGitClass, changed),
150 NULL, NULL,
151 g_cclosure_marshal_VOID__VOID,
152 G_TYPE_NONE, 0);
154 g_type_class_add_private (object_class, sizeof (GiggleGitPriv));
157 static void
158 giggle_git_init (GiggleGit *git)
160 GiggleGitPriv *priv;
162 priv = GET_PRIV (git);
164 priv->directory = NULL;
165 priv->dispatcher = giggle_dispatcher_new ();
167 priv->jobs = g_hash_table_new_full (g_direct_hash, g_direct_equal,
168 NULL,
169 (GDestroyNotify) git_job_data_free);
172 static void
173 foreach_job_cancel (gpointer key, GitJobData *data, GiggleGit *git)
175 GiggleGitPriv *priv;
177 priv = GET_PRIV (git);
179 giggle_dispatcher_cancel (priv->dispatcher, data->id);
182 static void
183 git_finalize (GObject *object)
185 GiggleGitPriv *priv;
187 priv = GET_PRIV (object);
189 g_hash_table_foreach (priv->jobs,
190 (GHFunc) foreach_job_cancel,
191 object);
193 g_hash_table_destroy (priv->jobs);
194 g_free (priv->directory);
195 g_free (priv->git_dir);
196 g_free (priv->project_dir);
197 g_free (priv->project_name);
199 g_object_unref (priv->dispatcher);
201 G_OBJECT_CLASS (giggle_git_parent_class)->finalize (object);
204 static void
205 git_get_property (GObject *object,
206 guint param_id,
207 GValue *value,
208 GParamSpec *pspec)
210 GiggleGitPriv *priv;
212 priv = GET_PRIV (object);
214 switch (param_id) {
215 case PROP_DESCRIPTION:
216 g_value_set_string (value, priv->description);
217 break;
218 case PROP_DIRECTORY:
219 g_value_set_string (value, priv->directory);
220 break;
221 case PROP_GIT_DIR:
222 g_value_set_string (value, priv->git_dir);
223 break;
224 case PROP_PROJECT_DIR:
225 g_value_set_string (value, priv->project_dir);
226 break;
227 case PROP_PROJECT_NAME:
228 g_value_set_string (value, priv->project_name);
229 break;
230 case PROP_REMOTES:
231 g_value_set_pointer (value, priv->remotes);
232 break;
233 default:
234 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
235 break;
239 static void
240 git_set_property (GObject *object,
241 guint param_id,
242 const GValue *value,
243 GParamSpec *pspec)
245 GiggleGitPriv *priv;
247 priv = GET_PRIV (object);
249 switch (param_id) {
250 default:
251 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
252 break;
256 static GQuark
257 giggle_git_error_quark (void)
259 return g_quark_from_string("GiggleGitError");
262 static gboolean
263 git_verify_directory (const gchar *directory,
264 gchar **git_dir,
265 GError **error)
267 /* Do some funky stuff to verify that it's a valid GIT repo */
268 gchar *argv[] = { GIT_COMMAND, "rev-parse", "--git-dir", NULL };
269 gchar *std_out = NULL;
270 gchar *std_err = NULL;
271 gint exit_code = 0;
272 gboolean verified = FALSE;
273 GError *local_error = NULL;
275 if(git_dir) {
276 *git_dir = NULL;
279 g_spawn_sync (directory, argv, NULL,
280 0, NULL, NULL,
281 &std_out, &std_err,
282 &exit_code, &local_error);
284 if (local_error) {
285 if (error) {
286 *error = local_error;
287 } else {
288 g_warning ("Problem while checking folder \"%s\" for being related to git: %s",
289 directory,
290 local_error->message);
291 g_error_free (local_error);
293 } else if (exit_code != 0) {
294 if (error) {
295 g_set_error (error, giggle_git_error_quark(), 0 /* error code */, "%s", std_err);
296 } else {
297 g_warning ("Problem while checking folder \"%s\": Unexpected exit code %d: %s",
298 directory, exit_code, std_err);
300 } else {
301 verified = TRUE;
303 if (git_dir) {
304 /* split into {dir, NULL} */
305 gchar** split = g_strsplit (std_out, "\n", 2);
306 if(!split || !*split) {
307 g_warning("Didn't get a good git directory for %s: %s", directory, std_out);
309 *git_dir = g_strdup(split ? *split : "");
310 g_strfreev (split);
312 if(!g_path_is_absolute(*git_dir)) {
313 gchar* full_path = g_build_path(G_DIR_SEPARATOR_S, directory, *git_dir, NULL);
314 g_free(*git_dir);
315 *git_dir = full_path;
320 g_free(std_out);
321 g_free(std_err);
323 return verified;
326 gboolean
327 giggle_git_test_dir (gchar const* dir)
329 return git_verify_directory (dir, NULL, NULL);
332 static void
333 git_job_data_free (GitJobData *data)
335 g_object_unref (data->job);
337 g_slice_free (GitJobData, data);
340 static void
341 git_execute_callback (GiggleDispatcher *dispatcher,
342 guint id,
343 GError *error,
344 const gchar *output_str,
345 gsize output_len,
346 GiggleGit *git)
348 GiggleGitPriv *priv;
349 GitJobData *data;
351 priv = GET_PRIV (git);
353 data = g_hash_table_lookup (priv->jobs, GINT_TO_POINTER (id));
354 g_assert (data != NULL);
356 if (!error) {
357 giggle_job_handle_output (data->job, output_str, output_len);
360 if (data->callback) {
361 data->callback (git, data->job, error, data->user_data);
364 if (data->destroy_notify && data->user_data) {
365 (data->destroy_notify) (data->user_data);
368 g_hash_table_remove (priv->jobs, GINT_TO_POINTER (id));
371 GiggleGit *
372 giggle_git_get (void)
374 static GiggleGit *git = NULL;
376 if (!git) {
377 git = g_object_new (GIGGLE_TYPE_GIT, NULL);
378 } else {
379 g_object_ref (git);
382 return git;
385 static gchar *
386 giggle_git_get_description_file (const GiggleGit *git)
388 GiggleGitPriv *priv = GET_PRIV (git);
390 return g_build_filename (priv->git_dir, "description", NULL);
393 static void
394 giggle_git_update_description (GiggleGit *git)
396 /* FIXME: read .git/description into description; install a file watch */
397 GiggleGitPriv *priv;
398 GError *error;
399 gchar* description;
401 priv = GET_PRIV (git);
402 g_free (priv->description);
403 priv->description = NULL;
405 description = giggle_git_get_description_file (git);
406 error = NULL;
407 if (!g_file_get_contents (description, &(priv->description), NULL, &error)) {
408 if (error) {
409 g_warning ("Couldn't read description file %s: %s", description, error->message);
410 g_error_free (error);
411 } else {
412 g_warning ("Couldn't read description file %s", description);
414 if(!priv->description) {
415 priv->description = g_strdup ("");
419 g_free (description);
421 g_object_notify (G_OBJECT (git), "description");
424 static void
425 giggle_git_update_remotes (GiggleGit* git)
427 GiggleGitPriv *priv;
428 gchar *path;
429 GDir *dir;
431 priv = GET_PRIV (git);
433 /* cleanup */
434 g_list_foreach (priv->remotes, (GFunc) g_object_unref, NULL);
435 g_list_free (priv->remotes);
436 priv->remotes = NULL;
438 /* list files and add them */
439 path = g_build_filename (priv->git_dir, "remotes", NULL);
440 dir = g_dir_open (path, 0, NULL);
442 if (dir) {
443 const gchar *file;
445 for(file = g_dir_read_name(dir); file; file = g_dir_read_name(dir)) {
446 gchar *filename = g_build_filename (path, file, NULL);
447 priv->remotes = g_list_prepend (priv->remotes, giggle_remote_new_from_file (filename));
448 g_free (filename);
451 g_dir_close (dir);
453 priv->remotes = g_list_reverse (priv->remotes);
454 g_free (path);
456 /* update */
457 g_object_notify (G_OBJECT (git), "remotes");
460 const gchar *
461 giggle_git_get_description (GiggleGit *git)
463 g_return_val_if_fail (GIGGLE_IS_GIT (git), NULL);
465 return GET_PRIV(git)->description;
468 void
469 giggle_git_write_description (GiggleGit *git,
470 const gchar *description) /* FIXME: add GError? */
472 GiggleGitPriv *priv;
473 GError *error;
474 gchar *filename;
476 g_return_if_fail (GIGGLE_IS_GIT (git));
478 priv = GET_PRIV (git);
479 if(description == priv->description) {
480 return;
483 g_free (priv->description);
484 priv->description = g_strdup (description);
486 error = NULL;
487 filename = giggle_git_get_description_file (git);
488 if (!g_file_set_contents (filename, priv->description, -1, &error)) {
489 if (error) {
490 g_warning ("Couldn't write description: %s", error->message);
491 g_error_free(error);
492 } else {
493 g_warning ("Couldn't write description");
496 g_free (filename);
498 /* notify will become unnecessary once we have file monitoring */
499 g_object_notify (G_OBJECT (git), "description");
502 const gchar *
503 giggle_git_get_directory (GiggleGit *git)
505 GiggleGitPriv *priv;
507 g_return_val_if_fail (GIGGLE_IS_GIT (git), NULL);
509 priv = GET_PRIV (git);
511 return priv->directory;
514 gboolean
515 giggle_git_set_directory (GiggleGit *git,
516 const gchar *directory,
517 GError **error)
519 GiggleGitPriv *priv;
520 gchar *tmp_dir;
521 gchar *suffix;
522 gchar *dir;
524 g_return_val_if_fail (GIGGLE_IS_GIT (git), FALSE);
525 g_return_val_if_fail (directory != NULL, FALSE);
527 priv = GET_PRIV (git);
529 if (!git_verify_directory (directory, &tmp_dir, error)) {
530 return FALSE;
533 /* update working directory */
534 dir = g_strdup (directory);
535 g_free (priv->directory);
536 priv->directory = dir;
538 /* update git dir */
539 g_free (priv->git_dir);
540 priv->git_dir = tmp_dir;
542 /* update project dir */
543 g_free (priv->project_dir);
545 tmp_dir = g_strdup (priv->git_dir);
546 suffix = g_strrstr (tmp_dir, ".git");
547 if(G_UNLIKELY(!suffix)) {
548 /* Tele-Tubby: Uuh Ooooh */
549 priv->project_dir = NULL;
550 } else {
551 /* .../giggle/.git
554 if(*(--suffix) == G_DIR_SEPARATOR) {
555 *suffix = '\0';
556 priv->project_dir = g_strdup (tmp_dir);
557 /* strdup again to skip truncated chars */
558 } else {
559 /* /home/herzi/Hacking/giggle.git - no project folder */
560 priv->project_dir = NULL;
563 g_free (tmp_dir);
565 /* update project name */
566 if (priv->project_dir) {
567 tmp_dir = g_path_get_basename (priv->project_dir);
568 } else {
569 suffix = g_strrstr (priv->git_dir, ".git");
570 if (suffix) {
571 *suffix = '\0';
572 tmp_dir = g_path_get_basename (priv->git_dir);
573 *suffix = '.'; /* restore */
574 } else {
575 tmp_dir = NULL;
578 g_free (priv->project_name);
579 priv->project_name = tmp_dir;
581 /* notify */
582 g_object_notify (G_OBJECT (git), "directory");
583 g_object_notify (G_OBJECT (git), "git-dir");
584 g_object_notify (G_OBJECT (git), "project-dir");
585 g_object_notify (G_OBJECT (git), "project-name");
587 giggle_git_update_description (git);
588 giggle_git_update_remotes (git);
590 return TRUE;
593 const gchar *
594 giggle_git_get_git_dir (GiggleGit *git)
596 GiggleGitPriv *priv;
598 g_return_val_if_fail (GIGGLE_IS_GIT (git), NULL);
600 priv = GET_PRIV (git);
602 return priv->git_dir;
605 const gchar *
606 giggle_git_get_project_dir (GiggleGit *git)
608 GiggleGitPriv *priv;
610 g_return_val_if_fail (GIGGLE_IS_GIT (git), NULL);
612 priv = GET_PRIV (git);
614 return priv->project_dir;
617 const gchar *
618 giggle_git_get_project_name (GiggleGit* git)
620 GiggleGitPriv *priv;
622 g_return_val_if_fail (GIGGLE_IS_GIT (git), NULL);
624 priv = GET_PRIV (git);
626 return priv->project_name;
629 GList *
630 giggle_git_get_remotes (GiggleGit *git)
632 g_return_val_if_fail (GIGGLE_IS_GIT (git), NULL);
634 return GET_PRIV (git)->remotes;
637 void
638 giggle_git_save_remote (GiggleGit *git,
639 GiggleRemote*remote)
641 GiggleGitPriv *priv;
642 gchar *path;
644 g_return_if_fail (GIGGLE_IS_GIT (git));
645 g_return_if_fail (GIGGLE_IS_REMOTE (remote));
647 priv = GET_PRIV (git);
648 path = g_build_filename (priv->git_dir, "remotes", giggle_remote_get_name (remote), NULL);
649 giggle_remote_save_to_file (remote, path);
650 g_free (path);
653 void
654 giggle_git_run_job_full (GiggleGit *git,
655 GiggleJob *job,
656 GiggleJobDoneCallback callback,
657 gpointer user_data,
658 GDestroyNotify destroy_notify)
660 GiggleGitPriv *priv;
661 gchar *command;
663 g_return_if_fail (GIGGLE_IS_GIT (git));
664 g_return_if_fail (GIGGLE_IS_JOB (job));
666 priv = GET_PRIV (git);
668 if (giggle_job_get_command_line (job, &command)) {
669 GitJobData *data;
671 data = g_slice_new0 (GitJobData);
672 data->id = giggle_dispatcher_execute (priv->dispatcher,
673 priv->project_dir,
674 command,
675 (GiggleExecuteCallback) git_execute_callback,
676 git);
678 data->job = g_object_ref (job);
679 data->callback = callback;
680 data->user_data = user_data;
681 data->destroy_notify = destroy_notify;
683 g_object_set (job, "id", data->id, NULL);
685 g_hash_table_insert (priv->jobs,
686 GINT_TO_POINTER (data->id), data);
687 } else {
688 g_warning ("Couldn't get command line for job");
691 g_free (command);
694 void
695 giggle_git_run_job (GiggleGit *git,
696 GiggleJob *job,
697 GiggleJobDoneCallback callback,
698 gpointer user_data)
700 giggle_git_run_job_full (git, job, callback, user_data, NULL);
703 void
704 giggle_git_cancel_job (GiggleGit *git, GiggleJob *job)
706 GiggleGitPriv *priv;
707 guint id;
709 g_return_if_fail (GIGGLE_IS_GIT (git));
710 g_return_if_fail (GIGGLE_IS_JOB (job));
712 priv = GET_PRIV (git);
714 g_object_get (job, "id", &id, NULL);
716 giggle_dispatcher_cancel (priv->dispatcher, id);
718 g_hash_table_remove (priv->jobs, GINT_TO_POINTER (id));
721 void
722 giggle_git_changed (GiggleGit *git)
724 g_return_if_fail (GIGGLE_IS_GIT (git));
726 g_signal_emit (git, signals[CHANGED], 0);