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 (GiggleGit
*git
,
62 const gchar
*directory
,
65 static void git_job_data_free (GitJobData
*data
);
66 static void git_execute_callback (GiggleDispatcher
*dispatcher
,
69 const gchar
*output_str
,
72 static GQuark
giggle_git_error_quark (void);
74 G_DEFINE_TYPE (GiggleGit
, giggle_git
, G_TYPE_OBJECT
)
86 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GIGGLE_TYPE_GIT, GiggleGitPriv))
89 giggle_git_class_init (GiggleGitClass
*class)
91 GObjectClass
*object_class
= G_OBJECT_CLASS (class);
93 object_class
->finalize
= git_finalize
;
94 object_class
->get_property
= git_get_property
;
95 object_class
->set_property
= git_set_property
;
97 g_object_class_install_property (object_class
,
99 g_param_spec_string ("description",
101 "The project's description",
104 g_object_class_install_property (object_class
,
106 g_param_spec_string ("directory",
108 "the working directory",
111 g_object_class_install_property (object_class
,
113 g_param_spec_string ("git-dir",
115 "The equivalent of $GIT_DIR",
118 g_object_class_install_property (object_class
,
120 g_param_spec_string ("project-dir",
122 "The location of the checkout currently being worked on",
125 g_object_class_install_property (object_class
,
127 g_param_spec_string ("project-name",
129 "The name of the project (guessed)",
132 g_object_class_install_property (object_class
,
134 g_param_spec_string ("remotes",
136 "The remote sources",
137 NULL
, G_PARAM_READABLE
));
139 g_type_class_add_private (object_class
, sizeof (GiggleGitPriv
));
143 giggle_git_init (GiggleGit
*git
)
147 priv
= GET_PRIV (git
);
149 priv
->directory
= NULL
;
150 priv
->dispatcher
= giggle_dispatcher_new ();
152 priv
->jobs
= g_hash_table_new_full (g_direct_hash
, g_direct_equal
,
154 (GDestroyNotify
) git_job_data_free
);
158 foreach_job_cancel (gpointer key
, GitJobData
*data
, GiggleGit
*git
)
162 priv
= GET_PRIV (git
);
164 giggle_dispatcher_cancel (priv
->dispatcher
, data
->id
);
168 git_finalize (GObject
*object
)
172 priv
= GET_PRIV (object
);
174 g_hash_table_foreach (priv
->jobs
,
175 (GHFunc
) foreach_job_cancel
,
178 g_hash_table_destroy (priv
->jobs
);
179 g_free (priv
->directory
);
180 g_free (priv
->git_dir
);
181 g_free (priv
->project_dir
);
182 g_free (priv
->project_name
);
184 g_object_unref (priv
->dispatcher
);
186 G_OBJECT_CLASS (giggle_git_parent_class
)->finalize (object
);
190 git_get_property (GObject
*object
,
197 priv
= GET_PRIV (object
);
200 case PROP_DESCRIPTION
:
201 g_value_set_string (value
, priv
->description
);
204 g_value_set_string (value
, priv
->directory
);
207 g_value_set_string (value
, priv
->git_dir
);
209 case PROP_PROJECT_DIR
:
210 g_value_set_string (value
, priv
->project_dir
);
212 case PROP_PROJECT_NAME
:
213 g_value_set_string (value
, priv
->project_name
);
216 g_value_set_pointer (value
, priv
->remotes
);
219 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
225 git_set_property (GObject
*object
,
232 priv
= GET_PRIV (object
);
236 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
242 giggle_git_error_quark (void)
244 return g_quark_from_string("GiggleGitError");
248 git_verify_directory (GiggleGit
*git
,
249 const gchar
*directory
,
253 /* Do some funky stuff to verify that it's a valid GIT repo */
254 gchar
*argv
[] = { GIT_COMMAND
, "rev-parse", "--git-dir", NULL
};
255 gchar
*std_out
= NULL
;
256 gchar
*std_err
= NULL
;
258 gboolean verified
= FALSE
;
259 GError
*local_error
= NULL
;
265 g_spawn_sync (directory
, argv
, NULL
,
268 &exit_code
, &local_error
);
272 *error
= local_error
;
274 g_warning ("Problem while checking folder \"%s\" for being related to git: %s",
276 local_error
->message
);
277 g_error_free (local_error
);
279 } else if (exit_code
!= 0) {
281 g_set_error (error
, giggle_git_error_quark(), 0 /* error code */, "%s", std_err
);
283 g_warning ("Problem while checking folder \"%s\": Unexpected exit code %d: %s",
284 directory
, exit_code
, std_err
);
290 /* split into {dir, NULL} */
291 gchar
** split
= g_strsplit (std_out
, "\n", 2);
292 if(!split
|| !*split
) {
293 g_warning("Didn't get a good git directory for %s: %s", directory
, std_out
);
295 *git_dir
= g_strdup(split
? *split
: "");
298 if(!g_path_is_absolute(*git_dir
)) {
299 gchar
* full_path
= g_build_path(G_DIR_SEPARATOR_S
, directory
, *git_dir
, NULL
);
301 *git_dir
= full_path
;
313 git_job_data_free (GitJobData
*data
)
315 g_object_unref (data
->job
);
317 g_slice_free (GitJobData
, data
);
321 git_execute_callback (GiggleDispatcher
*dispatcher
,
324 const gchar
*output_str
,
331 priv
= GET_PRIV (git
);
333 data
= g_hash_table_lookup (priv
->jobs
, GINT_TO_POINTER (id
));
334 g_assert (data
!= NULL
);
337 giggle_job_handle_output (data
->job
, output_str
, output_len
);
340 if (data
->callback
) {
341 data
->callback (git
, data
->job
, error
, data
->user_data
);
344 if (data
->destroy_notify
&& data
->user_data
) {
345 (data
->destroy_notify
) (data
->user_data
);
348 g_hash_table_remove (priv
->jobs
, GINT_TO_POINTER (id
));
352 giggle_git_get (void)
354 static GiggleGit
*git
= NULL
;
357 git
= g_object_new (GIGGLE_TYPE_GIT
, NULL
);
366 giggle_git_get_description_file (const GiggleGit
*git
)
368 GiggleGitPriv
*priv
= GET_PRIV (git
);
370 return g_build_filename (priv
->git_dir
, "description", NULL
);
374 giggle_git_update_description (GiggleGit
*git
)
376 // FIXME: read .git/description into description; install a file watch
381 priv
= GET_PRIV (git
);
382 g_free (priv
->description
);
383 priv
->description
= NULL
;
385 description
= giggle_git_get_description_file (git
);
387 if (!g_file_get_contents (description
, &(priv
->description
), NULL
, &error
)) {
389 g_warning ("Couldn't read description file %s: %s", description
, error
->message
);
390 g_error_free (error
);
392 g_warning ("Couldn't read description file %s", description
);
394 if(!priv
->description
) {
395 priv
->description
= g_strdup ("");
399 g_free (description
);
401 g_object_notify (G_OBJECT (git
), "description");
405 giggle_git_update_remotes (GiggleGit
* git
)
412 priv
= GET_PRIV (git
);
415 g_list_foreach (priv
->remotes
, (GFunc
) g_object_unref
, NULL
);
416 g_list_free (priv
->remotes
);
417 priv
->remotes
= NULL
;
419 /* list files and add them */
421 path
= g_build_filename (priv
->git_dir
, "remotes", NULL
);
422 dir
= g_dir_open (path
, 0, &error
);
425 g_warning ("Error loading remotes: %s", error
->message
);
429 for(file
= g_dir_read_name(dir
); file
; file
= g_dir_read_name(dir
)) {
430 gchar
*filename
= g_build_filename (path
, file
, NULL
);
431 priv
->remotes
= g_list_prepend (priv
->remotes
, giggle_remote_new_from_file (filename
));
437 priv
->remotes
= g_list_reverse (priv
->remotes
);
441 g_object_notify (G_OBJECT (git
), "remotes");
445 giggle_git_get_description (GiggleGit
*git
)
447 g_return_val_if_fail (GIGGLE_IS_GIT (git
), NULL
);
449 return GET_PRIV(git
)->description
;
453 giggle_git_write_description (GiggleGit
*git
,
454 const gchar
*description
) // FIXME: add GError?
460 g_return_if_fail (GIGGLE_IS_GIT (git
));
462 priv
= GET_PRIV (git
);
463 if(description
== priv
->description
) {
467 g_free (priv
->description
);
468 priv
->description
= g_strdup (description
);
471 filename
= giggle_git_get_description_file (git
);
472 if (!g_file_set_contents (filename
, priv
->description
, -1, &error
)) {
474 g_warning ("Couldn't write description: %s", error
->message
);
477 g_warning ("Couldn't write description");
482 /* notify will become unnecessary once we have file monitoring */
483 g_object_notify (G_OBJECT (git
), "description");
487 giggle_git_get_directory (GiggleGit
*git
)
491 g_return_val_if_fail (GIGGLE_IS_GIT (git
), NULL
);
493 priv
= GET_PRIV (git
);
495 return priv
->directory
;
499 giggle_git_set_directory (GiggleGit
*git
,
500 const gchar
*directory
,
507 g_return_val_if_fail (GIGGLE_IS_GIT (git
), FALSE
);
508 g_return_val_if_fail (directory
!= NULL
, FALSE
);
510 priv
= GET_PRIV (git
);
512 if (!git_verify_directory (git
, directory
, &tmp_dir
, error
)) {
516 /* update working directory */
517 g_free (priv
->directory
);
518 priv
->directory
= g_strdup (directory
);
521 g_free (priv
->git_dir
);
522 priv
->git_dir
= tmp_dir
;
524 /* update project dir */
525 g_free (priv
->project_dir
);
527 tmp_dir
= g_strdup (priv
->git_dir
);
528 suffix
= g_strrstr (tmp_dir
, ".git");
529 if(G_UNLIKELY(!suffix
)) {
530 /* Tele-Tubby: Uuh Ooooh */
531 priv
->project_dir
= NULL
;
535 if(*(--suffix
) == G_DIR_SEPARATOR
) {
537 priv
->project_dir
= g_strdup (tmp_dir
);
538 /* strdup again to skip truncated chars */
540 /* /home/herzi/Hacking/giggle.git - no project folder */
541 priv
->project_dir
= NULL
;
546 /* update project name */
547 if (priv
->project_dir
) {
548 tmp_dir
= g_path_get_basename (priv
->project_dir
);
550 suffix
= g_strrstr (priv
->git_dir
, ".git");
553 tmp_dir
= g_path_get_basename (priv
->git_dir
);
554 *suffix
= '.'; // restore
559 g_free (priv
->project_name
);
560 priv
->project_name
= tmp_dir
;
563 g_object_notify (G_OBJECT (git
), "directory");
564 g_object_notify (G_OBJECT (git
), "git-dir");
565 g_object_notify (G_OBJECT (git
), "project-dir");
566 g_object_notify (G_OBJECT (git
), "project-name");
568 giggle_git_update_description (git
);
569 giggle_git_update_remotes (git
);
575 giggle_git_get_git_dir (GiggleGit
*git
)
579 g_return_val_if_fail (GIGGLE_IS_GIT (git
), NULL
);
581 priv
= GET_PRIV (git
);
583 return priv
->git_dir
;
587 giggle_git_get_project_dir (GiggleGit
*git
)
591 g_return_val_if_fail (GIGGLE_IS_GIT (git
), NULL
);
593 priv
= GET_PRIV (git
);
595 return priv
->project_dir
;
599 giggle_git_get_project_name (GiggleGit
* git
)
603 g_return_val_if_fail (GIGGLE_IS_GIT (git
), NULL
);
605 priv
= GET_PRIV (git
);
607 return priv
->project_name
;
611 giggle_git_get_remotes (GiggleGit
*git
)
613 g_return_val_if_fail (GIGGLE_IS_GIT (git
), NULL
);
615 return GET_PRIV (git
)->remotes
;
619 giggle_git_save_remote (GiggleGit
*git
,
625 g_return_if_fail (GIGGLE_IS_GIT (git
));
626 g_return_if_fail (GIGGLE_IS_REMOTE (remote
));
628 priv
= GET_PRIV (git
);
629 path
= g_build_filename (priv
->git_dir
, "remotes", giggle_remote_get_name (remote
), NULL
);
630 giggle_remote_save_to_file (remote
, path
);
635 giggle_git_run_job_full (GiggleGit
*git
,
637 GiggleJobDoneCallback callback
,
639 GDestroyNotify destroy_notify
)
644 g_return_if_fail (GIGGLE_IS_GIT (git
));
645 g_return_if_fail (GIGGLE_IS_JOB (job
));
647 priv
= GET_PRIV (git
);
649 if (giggle_job_get_command_line (job
, &command
)) {
652 data
= g_slice_new0 (GitJobData
);
653 data
->id
= giggle_dispatcher_execute (priv
->dispatcher
,
656 (GiggleExecuteCallback
) git_execute_callback
,
659 data
->job
= g_object_ref (job
);
660 data
->callback
= callback
;
661 data
->user_data
= user_data
;
662 data
->destroy_notify
= destroy_notify
;
664 g_object_set (job
, "id", data
->id
, NULL
);
666 g_hash_table_insert (priv
->jobs
,
667 GINT_TO_POINTER (data
->id
), data
);
669 g_warning ("Couldn't get command line for job");
676 giggle_git_run_job (GiggleGit
*git
,
678 GiggleJobDoneCallback callback
,
681 giggle_git_run_job_full (git
, job
, callback
, user_data
, NULL
);
685 giggle_git_cancel_job (GiggleGit
*git
, GiggleJob
*job
)
690 g_return_if_fail (GIGGLE_IS_GIT (git
));
691 g_return_if_fail (GIGGLE_IS_JOB (job
));
693 priv
= GET_PRIV (git
);
695 g_object_get (job
, "id", &id
, NULL
);
697 giggle_dispatcher_cancel (priv
->dispatcher
, id
);
699 g_hash_table_remove (priv
->jobs
, GINT_TO_POINTER (id
));