1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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.
23 #include "giggle-dispatcher.h"
24 #include "giggle-git.h"
25 #include "giggle-remote.h"
29 typedef struct GiggleGitPriv GiggleGitPriv
;
31 struct GiggleGitPriv
{
32 GiggleDispatcher
*dispatcher
;
47 GiggleJobDoneCallback callback
;
49 GDestroyNotify destroy_notify
;
52 static void git_finalize (GObject
*object
);
53 static void git_get_property (GObject
*object
,
57 static void git_set_property (GObject
*object
,
61 static gboolean
git_verify_directory (const gchar
*directory
,
64 static void git_job_data_free (GitJobData
*data
);
65 static void git_execute_callback (GiggleDispatcher
*dispatcher
,
68 const gchar
*output_str
,
71 static GQuark
giggle_git_error_quark (void);
73 G_DEFINE_TYPE (GiggleGit
, giggle_git
, G_TYPE_OBJECT
)
90 static guint signals
[LAST_SIGNAL
] = { 0, };
92 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GIGGLE_TYPE_GIT, GiggleGitPriv))
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
,
105 g_param_spec_string ("description",
107 "The project's description",
110 g_object_class_install_property (object_class
,
112 g_param_spec_string ("directory",
114 "the working directory",
117 g_object_class_install_property (object_class
,
119 g_param_spec_string ("git-dir",
121 "The equivalent of $GIT_DIR",
124 g_object_class_install_property (object_class
,
126 g_param_spec_string ("project-dir",
128 "The location of the checkout currently being worked on",
131 g_object_class_install_property (object_class
,
133 g_param_spec_string ("project-name",
135 "The name of the project (guessed)",
138 g_object_class_install_property (object_class
,
140 g_param_spec_string ("remotes",
142 "The remote sources",
143 NULL
, G_PARAM_READABLE
));
146 g_signal_new ("changed",
147 G_OBJECT_CLASS_TYPE (object_class
),
149 G_STRUCT_OFFSET (GiggleGitClass
, changed
),
151 g_cclosure_marshal_VOID__VOID
,
154 g_type_class_add_private (object_class
, sizeof (GiggleGitPriv
));
158 giggle_git_init (GiggleGit
*git
)
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
,
169 (GDestroyNotify
) git_job_data_free
);
173 foreach_job_cancel (gpointer key
, GitJobData
*data
, GiggleGit
*git
)
177 priv
= GET_PRIV (git
);
179 giggle_dispatcher_cancel (priv
->dispatcher
, data
->id
);
183 git_finalize (GObject
*object
)
187 priv
= GET_PRIV (object
);
189 g_hash_table_foreach (priv
->jobs
,
190 (GHFunc
) foreach_job_cancel
,
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
);
205 git_get_property (GObject
*object
,
212 priv
= GET_PRIV (object
);
215 case PROP_DESCRIPTION
:
216 g_value_set_string (value
, priv
->description
);
219 g_value_set_string (value
, priv
->directory
);
222 g_value_set_string (value
, priv
->git_dir
);
224 case PROP_PROJECT_DIR
:
225 g_value_set_string (value
, priv
->project_dir
);
227 case PROP_PROJECT_NAME
:
228 g_value_set_string (value
, priv
->project_name
);
231 g_value_set_pointer (value
, priv
->remotes
);
234 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
240 git_set_property (GObject
*object
,
247 priv
= GET_PRIV (object
);
251 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
257 giggle_git_error_quark (void)
259 return g_quark_from_string("GiggleGitError");
263 git_verify_directory (const gchar
*directory
,
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
;
272 gboolean verified
= FALSE
;
273 GError
*local_error
= NULL
;
279 g_spawn_sync (directory
, argv
, NULL
,
282 &exit_code
, &local_error
);
286 *error
= local_error
;
288 g_warning ("Problem while checking folder \"%s\" for being related to git: %s",
290 local_error
->message
);
291 g_error_free (local_error
);
293 } else if (exit_code
!= 0) {
295 g_set_error (error
, giggle_git_error_quark(), 0 /* error code */, "%s", std_err
);
297 g_warning ("Problem while checking folder \"%s\": Unexpected exit code %d: %s",
298 directory
, exit_code
, std_err
);
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
: "");
312 if(!g_path_is_absolute(*git_dir
)) {
313 gchar
* full_path
= g_build_path(G_DIR_SEPARATOR_S
, directory
, *git_dir
, NULL
);
315 *git_dir
= full_path
;
327 giggle_git_test_dir (gchar
const* dir
)
329 return git_verify_directory (dir
, NULL
, NULL
);
333 git_job_data_free (GitJobData
*data
)
335 g_object_unref (data
->job
);
337 g_slice_free (GitJobData
, data
);
341 git_execute_callback (GiggleDispatcher
*dispatcher
,
344 const gchar
*output_str
,
351 priv
= GET_PRIV (git
);
353 data
= g_hash_table_lookup (priv
->jobs
, GINT_TO_POINTER (id
));
354 g_assert (data
!= NULL
);
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
));
372 giggle_git_get (void)
374 static GiggleGit
*git
= NULL
;
377 git
= g_object_new (GIGGLE_TYPE_GIT
, NULL
);
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
);
394 giggle_git_update_description (GiggleGit
*git
)
396 /* FIXME: read .git/description into description; install a file watch */
401 priv
= GET_PRIV (git
);
402 g_free (priv
->description
);
403 priv
->description
= NULL
;
405 description
= giggle_git_get_description_file (git
);
407 if (!g_file_get_contents (description
, &(priv
->description
), NULL
, &error
)) {
409 g_warning ("Couldn't read description file %s: %s", description
, error
->message
);
410 g_error_free (error
);
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");
425 giggle_git_update_remotes (GiggleGit
* git
)
431 priv
= GET_PRIV (git
);
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
);
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
));
453 priv
->remotes
= g_list_reverse (priv
->remotes
);
457 g_object_notify (G_OBJECT (git
), "remotes");
461 giggle_git_get_description (GiggleGit
*git
)
463 g_return_val_if_fail (GIGGLE_IS_GIT (git
), NULL
);
465 return GET_PRIV(git
)->description
;
469 giggle_git_write_description (GiggleGit
*git
,
470 const gchar
*description
) /* FIXME: add GError? */
476 g_return_if_fail (GIGGLE_IS_GIT (git
));
478 priv
= GET_PRIV (git
);
479 if(description
== priv
->description
) {
483 g_free (priv
->description
);
484 priv
->description
= g_strdup (description
);
487 filename
= giggle_git_get_description_file (git
);
488 if (!g_file_set_contents (filename
, priv
->description
, -1, &error
)) {
490 g_warning ("Couldn't write description: %s", error
->message
);
493 g_warning ("Couldn't write description");
498 /* notify will become unnecessary once we have file monitoring */
499 g_object_notify (G_OBJECT (git
), "description");
503 giggle_git_get_directory (GiggleGit
*git
)
507 g_return_val_if_fail (GIGGLE_IS_GIT (git
), NULL
);
509 priv
= GET_PRIV (git
);
511 return priv
->directory
;
515 giggle_git_set_directory (GiggleGit
*git
,
516 const gchar
*directory
,
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
)) {
533 /* update working directory */
534 dir
= g_strdup (directory
);
535 g_free (priv
->directory
);
536 priv
->directory
= 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
;
554 if(*(--suffix
) == G_DIR_SEPARATOR
) {
556 priv
->project_dir
= g_strdup (tmp_dir
);
557 /* strdup again to skip truncated chars */
559 /* /home/herzi/Hacking/giggle.git - no project folder */
560 priv
->project_dir
= NULL
;
565 /* update project name */
566 if (priv
->project_dir
) {
567 tmp_dir
= g_path_get_basename (priv
->project_dir
);
569 suffix
= g_strrstr (priv
->git_dir
, ".git");
572 tmp_dir
= g_path_get_basename (priv
->git_dir
);
573 *suffix
= '.'; /* restore */
578 g_free (priv
->project_name
);
579 priv
->project_name
= tmp_dir
;
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
);
594 giggle_git_get_git_dir (GiggleGit
*git
)
598 g_return_val_if_fail (GIGGLE_IS_GIT (git
), NULL
);
600 priv
= GET_PRIV (git
);
602 return priv
->git_dir
;
606 giggle_git_get_project_dir (GiggleGit
*git
)
610 g_return_val_if_fail (GIGGLE_IS_GIT (git
), NULL
);
612 priv
= GET_PRIV (git
);
614 return priv
->project_dir
;
618 giggle_git_get_project_name (GiggleGit
* git
)
622 g_return_val_if_fail (GIGGLE_IS_GIT (git
), NULL
);
624 priv
= GET_PRIV (git
);
626 return priv
->project_name
;
630 giggle_git_get_remotes (GiggleGit
*git
)
632 g_return_val_if_fail (GIGGLE_IS_GIT (git
), NULL
);
634 return GET_PRIV (git
)->remotes
;
638 giggle_git_save_remote (GiggleGit
*git
,
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
);
654 giggle_git_run_job_full (GiggleGit
*git
,
656 GiggleJobDoneCallback callback
,
658 GDestroyNotify destroy_notify
)
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
)) {
671 data
= g_slice_new0 (GitJobData
);
672 data
->id
= giggle_dispatcher_execute (priv
->dispatcher
,
675 (GiggleExecuteCallback
) git_execute_callback
,
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
);
688 g_warning ("Couldn't get command line for job");
695 giggle_git_run_job (GiggleGit
*git
,
697 GiggleJobDoneCallback callback
,
700 giggle_git_run_job_full (git
, job
, callback
, user_data
, NULL
);
704 giggle_git_cancel_job (GiggleGit
*git
, GiggleJob
*job
)
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
));
722 giggle_git_changed (GiggleGit
*git
)
724 g_return_if_fail (GIGGLE_IS_GIT (git
));
726 g_signal_emit (git
, signals
[CHANGED
], 0);