1 /* ide-vcs.c
2  *
3  * Copyright 2015-2019 Christian Hergert <christian@hergert.me>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
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
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "ide-vcs"
22 
23 #include "config.h"
24 
25 #include <libide-io.h>
26 #include <string.h>
27 
28 #include "ide-directory-vcs.h"
29 #include "ide-vcs.h"
30 #include "ide-vcs-enums.h"
31 
32 G_DEFINE_INTERFACE (IdeVcs, ide_vcs, IDE_TYPE_OBJECT)
33 
34 enum {
35   CHANGED,
36   N_SIGNALS
37 };
38 
39 static guint signals [N_SIGNALS];
40 
41 static void
ide_vcs_real_list_status_async(IdeVcs * self,GFile * directory_or_file,gboolean include_descendants,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)42 ide_vcs_real_list_status_async (IdeVcs              *self,
43                                 GFile               *directory_or_file,
44                                 gboolean             include_descendants,
45                                 gint                 io_priority,
46                                 GCancellable        *cancellable,
47                                 GAsyncReadyCallback  callback,
48                                 gpointer             user_data)
49 {
50   g_task_report_new_error (self,
51                            callback,
52                            user_data,
53                            ide_vcs_real_list_status_async,
54                            G_IO_ERROR,
55                            G_IO_ERROR_NOT_SUPPORTED,
56                            "Not supported by %s",
57                            G_OBJECT_TYPE_NAME (self));
58 }
59 
60 static GListModel *
ide_vcs_real_list_status_finish(IdeVcs * self,GAsyncResult * result,GError ** error)61 ide_vcs_real_list_status_finish (IdeVcs        *self,
62                                  GAsyncResult  *result,
63                                  GError       **error)
64 {
65   return g_task_propagate_pointer (G_TASK (result), error);
66 }
67 
68 static void
ide_vcs_real_list_branches_async(IdeVcs * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)69 ide_vcs_real_list_branches_async (IdeVcs              *self,
70                                   GCancellable        *cancellable,
71                                   GAsyncReadyCallback  callback,
72                                   gpointer             user_data)
73 {
74   g_task_report_new_error (self,
75                            callback,
76                            user_data,
77                            ide_vcs_real_list_branches_async,
78                            G_IO_ERROR,
79                            G_IO_ERROR_NOT_SUPPORTED,
80                            "Not supported by %s",
81                            G_OBJECT_TYPE_NAME (self));
82 }
83 
84 static GPtrArray *
ide_vcs_real_list_branches_finish(IdeVcs * self,GAsyncResult * result,GError ** error)85 ide_vcs_real_list_branches_finish (IdeVcs        *self,
86                                    GAsyncResult  *result,
87                                    GError       **error)
88 {
89   return g_task_propagate_pointer (G_TASK (result), error);
90 }
91 
92 static void
ide_vcs_real_list_tags_async(IdeVcs * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)93 ide_vcs_real_list_tags_async (IdeVcs              *self,
94                               GCancellable        *cancellable,
95                               GAsyncReadyCallback  callback,
96                               gpointer             user_data)
97 {
98   g_task_report_new_error (self,
99                            callback,
100                            user_data,
101                            ide_vcs_real_list_tags_async,
102                            G_IO_ERROR,
103                            G_IO_ERROR_NOT_SUPPORTED,
104                            "Not supported by %s",
105                            G_OBJECT_TYPE_NAME (self));
106 }
107 
108 static GPtrArray *
ide_vcs_real_list_tags_finish(IdeVcs * self,GAsyncResult * result,GError ** error)109 ide_vcs_real_list_tags_finish (IdeVcs        *self,
110                                GAsyncResult  *result,
111                                GError       **error)
112 {
113   return g_task_propagate_pointer (G_TASK (result), error);
114 }
115 
116 static void
ide_vcs_real_switch_branch_async(IdeVcs * self,IdeVcsBranch * branch,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)117 ide_vcs_real_switch_branch_async (IdeVcs              *self,
118                                   IdeVcsBranch        *branch,
119                                   GCancellable        *cancellable,
120                                   GAsyncReadyCallback  callback,
121                                   gpointer             user_data)
122 {
123   g_task_report_new_error (self,
124                            callback,
125                            user_data,
126                            ide_vcs_real_switch_branch_async,
127                            G_IO_ERROR,
128                            G_IO_ERROR_NOT_SUPPORTED,
129                            "Not supported by %s",
130                            G_OBJECT_TYPE_NAME (self));
131 }
132 
133 static gboolean
ide_vcs_real_switch_branch_finish(IdeVcs * self,GAsyncResult * result,GError ** error)134 ide_vcs_real_switch_branch_finish (IdeVcs        *self,
135                                    GAsyncResult  *result,
136                                    GError       **error)
137 {
138   return g_task_propagate_boolean (G_TASK (result), error);
139 }
140 
141 static void
ide_vcs_real_push_branch_async(IdeVcs * self,IdeVcsBranch * branch,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)142 ide_vcs_real_push_branch_async (IdeVcs              *self,
143                                 IdeVcsBranch        *branch,
144                                 GCancellable        *cancellable,
145                                 GAsyncReadyCallback  callback,
146                                 gpointer             user_data)
147 {
148   g_task_report_new_error (self,
149                            callback,
150                            user_data,
151                            ide_vcs_real_push_branch_async,
152                            G_IO_ERROR,
153                            G_IO_ERROR_NOT_SUPPORTED,
154                            "Not supported by %s",
155                            G_OBJECT_TYPE_NAME (self));
156 }
157 
158 static gboolean
ide_vcs_real_push_branch_finish(IdeVcs * self,GAsyncResult * result,GError ** error)159 ide_vcs_real_push_branch_finish (IdeVcs        *self,
160                                  GAsyncResult  *result,
161                                  GError       **error)
162 {
163   return g_task_propagate_boolean (G_TASK (result), error);
164 }
165 
166 static void
ide_vcs_default_init(IdeVcsInterface * iface)167 ide_vcs_default_init (IdeVcsInterface *iface)
168 {
169   iface->list_status_async = ide_vcs_real_list_status_async;
170   iface->list_status_finish = ide_vcs_real_list_status_finish;
171   iface->list_branches_async = ide_vcs_real_list_branches_async;
172   iface->list_branches_finish = ide_vcs_real_list_branches_finish;
173   iface->list_tags_async = ide_vcs_real_list_tags_async;
174   iface->list_tags_finish = ide_vcs_real_list_tags_finish;
175   iface->switch_branch_async = ide_vcs_real_switch_branch_async;
176   iface->switch_branch_finish = ide_vcs_real_switch_branch_finish;
177   iface->push_branch_async = ide_vcs_real_push_branch_async;
178   iface->push_branch_finish = ide_vcs_real_push_branch_finish;
179 
180   g_object_interface_install_property (iface,
181                                        g_param_spec_string ("branch-name",
182                                                             "Branch Name",
183                                                             "The current name of the branch",
184                                                             NULL,
185                                                             (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
186 
187   g_object_interface_install_property (iface,
188                                        g_param_spec_object ("workdir",
189                                                             "Working Directory",
190                                                             "The working directory for the VCS",
191                                                             G_TYPE_FILE,
192                                                             (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
193 
194   /**
195    * IdeVcs::changed:
196    *
197    * The "changed" signal should be emitted when the VCS has detected a change
198    * to the underlying VCS storage. This can be used by consumers to reload
199    * their respective data structures.
200    *
201    * Since: 3.32
202    */
203   signals [CHANGED] =
204     g_signal_new ("changed",
205                   G_TYPE_FROM_INTERFACE (iface),
206                   G_SIGNAL_RUN_LAST,
207                   G_STRUCT_OFFSET (IdeVcsInterface, changed),
208                   NULL, NULL,
209                   g_cclosure_marshal_VOID__VOID,
210                   G_TYPE_NONE, 0);
211   g_signal_set_va_marshaller (signals [CHANGED],
212                               G_TYPE_FROM_INTERFACE (iface),
213                               g_cclosure_marshal_VOID__VOIDv);
214 }
215 
216 /**
217  * ide_vcs_is_ignored:
218  * @self: An #IdeVcs
219  * @file: (nullable): a #GFile
220  * @error: A location for a #GError, or %NULL
221  *
222  * This function will check if @file is considered an "ignored file" by
223  * the underlying Version Control System.
224  *
225  * For convenience, this function will return %TRUE if @file is %NULL.
226  *
227  * If @self is %NULL, only static checks against known ignored files
228  * will be performed (such as .git, .flatpak-builder, etc).
229  *
230  * Returns: %TRUE if the path should be ignored.
231  *
232  * Thread safety: This function is safe to call from a thread as
233  *   #IdeVcs implementations are required to ensure this function
234  *   is thread-safe.
235  *
236  * Since: 3.32
237  */
238 gboolean
ide_vcs_is_ignored(IdeVcs * self,GFile * file,GError ** error)239 ide_vcs_is_ignored (IdeVcs  *self,
240                     GFile   *file,
241                     GError **error)
242 {
243   g_return_val_if_fail (!self || IDE_IS_VCS (self), FALSE);
244   g_return_val_if_fail (!file || G_IS_FILE (file), FALSE);
245 
246   if (file == NULL)
247     return TRUE;
248 
249   if (ide_g_file_is_ignored (file))
250     return TRUE;
251 
252   if (self != NULL)
253     {
254       if (IDE_VCS_GET_IFACE (self)->is_ignored)
255         return IDE_VCS_GET_IFACE (self)->is_ignored (self, file, error);
256     }
257 
258   return FALSE;
259 }
260 
261 /**
262  * ide_vcs_path_is_ignored:
263  * @self: An #IdeVcs
264  * @path: (nullable): The path to check
265  * @error: A location for a #GError, or %NULL
266  *
267  * This function acts like ide_vcs_is_ignored() except that it
268  * allows for using a regular file-system path.
269  *
270  * It will check if the path is absolute or relative to the project
271  * directory and adjust as necessary.
272  *
273  * For convenience, this function will return %TRUE if @path is %NULL.
274  *
275  * If @self is %NULL, only registered ignore patterns will be checked.
276  *
277  * Returns: %TRUE if the path should be ignored.
278  *
279  * Thread safety: This function is safe to call from a thread as
280  *   #IdeVcs implementations are required to ensure this function
281  *   is thread-safe.
282  *
283  * Since: 3.32
284  */
285 gboolean
ide_vcs_path_is_ignored(IdeVcs * self,const gchar * path,GError ** error)286 ide_vcs_path_is_ignored (IdeVcs       *self,
287                          const gchar  *path,
288                          GError      **error)
289 {
290   gboolean ret = FALSE;
291 
292   g_return_val_if_fail (!self || IDE_IS_VCS (self), FALSE);
293 
294   if (path == NULL)
295     return TRUE;
296 
297   if (ide_path_is_ignored (path))
298     return TRUE;
299 
300   if (self != NULL)
301     {
302       if (!ret && IDE_VCS_GET_IFACE (self)->is_ignored)
303         {
304           g_autoptr(GFile) file = NULL;
305 
306           if (g_path_is_absolute (path))
307             file = g_file_new_for_path (path);
308           else
309             file = g_file_get_child (ide_vcs_get_workdir (self), path);
310 
311           ret = IDE_VCS_GET_IFACE (self)->is_ignored (self, file, error);
312         }
313     }
314 
315   return ret;
316 }
317 
318 gint
ide_vcs_get_priority(IdeVcs * self)319 ide_vcs_get_priority (IdeVcs *self)
320 {
321   gint ret = 0;
322 
323   g_return_val_if_fail (IDE_IS_VCS (self), 0);
324 
325   if (IDE_VCS_GET_IFACE (self)->get_priority)
326     ret = IDE_VCS_GET_IFACE (self)->get_priority (self);
327 
328   return ret;
329 }
330 
331 /**
332  * ide_vcs_get_workdir:
333  * @self: An #IdeVcs.
334  *
335  * Retrieves the working directory for the context. This is the root of where
336  * the project files exist.
337  *
338  * This function is safe to call from threads holding a reference to @self.
339  *
340  * Returns: (transfer none): a #GFile.
341  *
342  * Since: 3.32
343  *
344  * Thread safety: this function is safe to call from threads. The working
345  *   directory should only be set at creating and therefore safe to call
346  *   at any time from any thread that holds a reference to @self. Those
347  *   implementing #IdeVcs are required to ensure this invariant holds true.
348  */
349 GFile *
ide_vcs_get_workdir(IdeVcs * self)350 ide_vcs_get_workdir (IdeVcs *self)
351 {
352   g_return_val_if_fail (IDE_IS_VCS (self), NULL);
353 
354   if (IDE_VCS_GET_IFACE (self)->get_workdir)
355     return IDE_VCS_GET_IFACE (self)->get_workdir (self);
356 
357   return NULL;
358 }
359 
360 void
ide_vcs_emit_changed(IdeVcs * self)361 ide_vcs_emit_changed (IdeVcs *self)
362 {
363   g_return_if_fail (IDE_IS_VCS (self));
364 
365   g_signal_emit (self, signals [CHANGED], 0);
366 }
367 
368 /**
369  * ide_vcs_get_config:
370  *
371  * Retrieves an #IdeVcsConfig for the #IdeVcs provided. If the #IdeVcs implementation does not
372  * support access to configuration, then %NULL is returned.
373  *
374  * Returns: (transfer full) (nullable): An #IdeVcsConfig or %NULL.
375  *
376  * Since: 3.32
377  */
378 IdeVcsConfig *
ide_vcs_get_config(IdeVcs * self)379 ide_vcs_get_config (IdeVcs *self)
380 {
381   IdeVcsConfig *ret = NULL;
382 
383   g_return_val_if_fail (IDE_IS_VCS (self), NULL);
384 
385   if (IDE_VCS_GET_IFACE (self)->get_config)
386     ret = IDE_VCS_GET_IFACE (self)->get_config (self);
387 
388   g_return_val_if_fail (!ret || IDE_IS_VCS_CONFIG (ret), NULL);
389 
390   return  ret;
391 }
392 
393 /**
394  * ide_vcs_get_branch_name:
395  *
396  * Retrieves the name of the branch in the current working directory.
397  *
398  * Returns: (transfer full): A string containing the branch name.
399  *
400  * Since: 3.32
401  */
402 gchar *
ide_vcs_get_branch_name(IdeVcs * self)403 ide_vcs_get_branch_name (IdeVcs *self)
404 {
405   gchar *ret = NULL;
406 
407   g_return_val_if_fail (IDE_IS_VCS (self), NULL);
408 
409   ide_object_lock (IDE_OBJECT (self));
410   if (IDE_VCS_GET_IFACE (self)->get_branch_name)
411     ret = IDE_VCS_GET_IFACE (self)->get_branch_name (self);
412   ide_object_unlock (IDE_OBJECT (self));
413 
414   if (ret == NULL)
415     ret = g_strdup ("primary");
416 
417   return ret;
418 }
419 
420 /**
421  * ide_vcs_list_status_async:
422  * @self: a #IdeVcs
423  * @directory_or_file: a #GFile containing a file or directory within the
424  *   working tree to retrieve the status of.
425  * @include_descendants: if descendants of @directory_or_file should be
426  *   included when retrieving status information.
427  * @io_priority: a priority for the IO, such as %G_PRIORITY_DEFAULT.
428  * @cancellable: (nullable): A #GCancellable or %NULL
429  * @callback: a callback for the operation
430  * @user_data: closure data for @callback
431  *
432  * Retrieves the status of the files matching the request. If
433  * @directory_or_file is a directory, then all files within that directory
434  * will be scanned for changes. If @include_descendants is %TRUE, the
435  * #IdeVcs will scan sub-directories for changes as well.
436  *
437  * The function specified by @callback should call ide_vcs_list_status_finish()
438  * to retrieve the result of this asynchronous operation.
439  *
440  * Since: 3.32
441  */
442 void
ide_vcs_list_status_async(IdeVcs * self,GFile * directory_or_file,gboolean include_descendants,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)443 ide_vcs_list_status_async (IdeVcs              *self,
444                            GFile               *directory_or_file,
445                            gboolean             include_descendants,
446                            gint                 io_priority,
447                            GCancellable        *cancellable,
448                            GAsyncReadyCallback  callback,
449                            gpointer             user_data)
450 {
451   g_return_if_fail (IDE_IS_VCS (self));
452   g_return_if_fail (!directory_or_file || G_IS_FILE (directory_or_file));
453   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
454 
455   if (directory_or_file == NULL)
456     directory_or_file = ide_vcs_get_workdir (self);
457 
458   IDE_VCS_GET_IFACE (self)->list_status_async (self,
459                                                directory_or_file,
460                                                include_descendants,
461                                                io_priority,
462                                                cancellable,
463                                                callback,
464                                                user_data);
465 }
466 
467 /**
468  * ide_vcs_list_status_finish:
469  * @self: a #IdeVcs
470  * @result: a #GAsyncResult provided to the callback
471  * @error: a location for a #GError
472  *
473  * Completes an asynchronous request to ide_vcs_list_status_async().
474  *
475  * The result of this function is a #GListModel containing objects that are
476  * #IdeVcsFileInfo.
477  *
478  * Returns: (transfer full) (nullable):
479  *   A #GListModel containing an #IdeVcsFileInfo for each of the files scanned
480  *   by the #IdeVcs. Upon failure, %NULL is returned and @error is set.
481  *
482  * Since: 3.32
483  */
484 GListModel *
ide_vcs_list_status_finish(IdeVcs * self,GAsyncResult * result,GError ** error)485 ide_vcs_list_status_finish (IdeVcs        *self,
486                             GAsyncResult  *result,
487                             GError       **error)
488 {
489   g_return_val_if_fail (IDE_IS_VCS (self), NULL);
490   g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
491 
492   return IDE_VCS_GET_IFACE (self)->list_status_finish (self, result, error);
493 }
494 
495 /**
496  * ide_vcs_from_context:
497  * @context: an #IdeContext
498  *
499  * Gets the #IdeVcs for the context.
500  *
501  * Returns: (transfer none): an #IdeVcs
502  *
503  * Since: 3.32
504  */
505 IdeVcs *
ide_vcs_from_context(IdeContext * context)506 ide_vcs_from_context (IdeContext *context)
507 {
508   IdeVcs *ret;
509 
510   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
511   g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
512 
513   /* Release full reference, into borrowed ref */
514   ret = ide_vcs_ref_from_context (context);
515   g_object_unref (ret);
516 
517   return ret;
518 }
519 
520 /**
521  * ide_vcs_ref_from_context:
522  * @context: an #IdeContext
523  *
524  * A thread-safe version of ide_vcs_from_context().
525  *
526  * Returns: (transfer full): an #IdeVcs
527  *
528  * Since: 3.32
529  */
530 IdeVcs *
ide_vcs_ref_from_context(IdeContext * context)531 ide_vcs_ref_from_context (IdeContext *context)
532 {
533   IdeVcs *ret;
534 
535   g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
536 
537   ide_object_lock (IDE_OBJECT (context));
538   ret = ide_object_get_child_typed (IDE_OBJECT (context), IDE_TYPE_VCS);
539   if (ret == NULL)
540     {
541       g_autoptr(GFile) workdir = ide_context_ref_workdir (context);
542       ret = (IdeVcs *)ide_directory_vcs_new (workdir);
543       ide_object_prepend (IDE_OBJECT (context), IDE_OBJECT (ret));
544     }
545   ide_object_unlock (IDE_OBJECT (context));
546 
547   return g_steal_pointer (&ret);
548 }
549 
550 void
ide_vcs_list_branches_async(IdeVcs * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)551 ide_vcs_list_branches_async (IdeVcs              *self,
552                              GCancellable        *cancellable,
553                              GAsyncReadyCallback  callback,
554                              gpointer             user_data)
555 {
556   g_return_if_fail (IDE_IS_VCS (self));
557   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
558 
559   IDE_VCS_GET_IFACE (self)->list_branches_async (self, cancellable, callback, user_data);
560 }
561 
562 /**
563  * ide_vcs_list_branches_finish:
564  * @self: an #IdeVcs
565  * @result: a #GAsyncResult
566  * @error: location for a #GError
567  *
568  * Returns: (transfer full) (element-type IdeVcsBranch): an array of
569  *   #IdeVcsBranch.
570  *
571  * Since: 3.32
572  */
573 GPtrArray *
ide_vcs_list_branches_finish(IdeVcs * self,GAsyncResult * result,GError ** error)574 ide_vcs_list_branches_finish (IdeVcs        *self,
575                               GAsyncResult  *result,
576                               GError       **error)
577 {
578   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
579   g_return_val_if_fail (IDE_IS_VCS (self), NULL);
580   g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
581 
582   return IDE_VCS_GET_IFACE (self)->list_branches_finish (self, result, error);
583 }
584 
585 void
ide_vcs_list_tags_async(IdeVcs * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)586 ide_vcs_list_tags_async (IdeVcs              *self,
587                          GCancellable        *cancellable,
588                          GAsyncReadyCallback  callback,
589                          gpointer             user_data)
590 {
591   g_return_if_fail (IDE_IS_VCS (self));
592   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
593 
594   IDE_VCS_GET_IFACE (self)->list_tags_async (self, cancellable, callback, user_data);
595 }
596 
597 /**
598  * ide_vcs_list_tags_finish:
599  * @self: an #IdeVcs
600  * @result: a #GAsyncResult
601  * @error: location for a #GError
602  *
603  * Returns: (transfer full) (element-type IdeVcsBranch): an array of
604  *   #IdeVcsBranch.
605  *
606  * Since: 3.32
607  */
608 GPtrArray *
ide_vcs_list_tags_finish(IdeVcs * self,GAsyncResult * result,GError ** error)609 ide_vcs_list_tags_finish (IdeVcs        *self,
610                           GAsyncResult  *result,
611                           GError       **error)
612 {
613   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
614   g_return_val_if_fail (IDE_IS_VCS (self), NULL);
615   g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
616 
617   return IDE_VCS_GET_IFACE (self)->list_tags_finish (self, result, error);
618 }
619 
620 void
ide_vcs_switch_branch_async(IdeVcs * self,IdeVcsBranch * branch,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)621 ide_vcs_switch_branch_async (IdeVcs              *self,
622                              IdeVcsBranch        *branch,
623                              GCancellable        *cancellable,
624                              GAsyncReadyCallback  callback,
625                              gpointer             user_data)
626 {
627   g_return_if_fail (IDE_IS_MAIN_THREAD ());
628   g_return_if_fail (IDE_IS_VCS (self));
629   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
630 
631   IDE_VCS_GET_IFACE (self)->switch_branch_async (self, branch, cancellable, callback, user_data);
632 }
633 
634 gboolean
ide_vcs_switch_branch_finish(IdeVcs * self,GAsyncResult * result,GError ** error)635 ide_vcs_switch_branch_finish (IdeVcs        *self,
636                               GAsyncResult  *result,
637                               GError       **error)
638 {
639   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
640   g_return_val_if_fail (IDE_IS_VCS (self), FALSE);
641   g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
642 
643   return IDE_VCS_GET_IFACE (self)->switch_branch_finish (self, result, error);
644 }
645 
646 void
ide_vcs_push_branch_async(IdeVcs * self,IdeVcsBranch * branch,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)647 ide_vcs_push_branch_async (IdeVcs              *self,
648                            IdeVcsBranch        *branch,
649                            GCancellable        *cancellable,
650                            GAsyncReadyCallback  callback,
651                            gpointer             user_data)
652 {
653   g_return_if_fail (IDE_IS_MAIN_THREAD ());
654   g_return_if_fail (IDE_IS_VCS (self));
655   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
656 
657   IDE_VCS_GET_IFACE (self)->push_branch_async (self, branch, cancellable, callback, user_data);
658 }
659 
660 gboolean
ide_vcs_push_branch_finish(IdeVcs * self,GAsyncResult * result,GError ** error)661 ide_vcs_push_branch_finish (IdeVcs        *self,
662                             GAsyncResult  *result,
663                             GError       **error)
664 {
665   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
666   g_return_val_if_fail (IDE_IS_VCS (self), FALSE);
667   g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
668 
669   return IDE_VCS_GET_IFACE (self)->push_branch_finish (self, result, error);
670 }
671