1 /*
2 * ggit-index.c
3 * This file is part of libgit2-glib
4 *
5 * Copyright (C) 2012 - Jesse van den Kieboom
6 *
7 * libgit2-glib is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * libgit2-glib is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with libgit2-glib. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "ggit-index.h"
22 #include <git2.h>
23 #include "ggit-error.h"
24 #include "ggit-repository.h"
25 #include "ggit-index-entry.h"
26 #include "ggit-index-entry-resolve-undo.h"
27
28 /**
29 * GgitIndex:
30 *
31 * Represents an index object.
32 */
33 struct _GgitIndex
34 {
35 GgitNative parent_instance;
36
37 GFile *file;
38 };
39
40 enum
41 {
42 PROP_0,
43 PROP_FILE
44 };
45
46 static void ggit_index_initable_iface_init (GInitableIface *iface);
47
48 G_DEFINE_TYPE_EXTENDED (GgitIndex, ggit_index, GGIT_TYPE_NATIVE,
49 0,
50 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
51 ggit_index_initable_iface_init))
52
53 static void
ggit_index_dispose(GObject * object)54 ggit_index_dispose (GObject *object)
55 {
56 GgitIndex *index = GGIT_INDEX (object);
57
58 if (index->file)
59 {
60 g_object_unref (index->file);
61 index->file = NULL;
62 }
63
64 G_OBJECT_CLASS (ggit_index_parent_class)->dispose (object);
65 }
66
67 static void
ggit_index_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)68 ggit_index_set_property (GObject *object,
69 guint prop_id,
70 const GValue *value,
71 GParamSpec *pspec)
72 {
73 GgitIndex *index = GGIT_INDEX (object);
74
75 switch (prop_id)
76 {
77 case PROP_FILE:
78 {
79 GFile *f;
80
81 f = g_value_get_object (value);
82
83 if (f != NULL)
84 {
85 index->file = g_file_dup (f);
86 }
87
88 break;
89 }
90 default:
91 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
92 break;
93 }
94 }
95
96 static void
ggit_index_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)97 ggit_index_get_property (GObject *object,
98 guint prop_id,
99 GValue *value,
100 GParamSpec *pspec)
101 {
102 GgitIndex *index = GGIT_INDEX (object);
103
104 switch (prop_id)
105 {
106 case PROP_FILE:
107 g_value_set_object (value, index->file);
108 break;
109 default:
110 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
111 break;
112 }
113 }
114
115 static gboolean
ggit_index_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)116 ggit_index_initable_init (GInitable *initable,
117 GCancellable *cancellable,
118 GError **error)
119 {
120 GgitIndex *index = GGIT_INDEX (initable);
121 gchar *path = NULL;
122 git_index *idx;
123 gint err;
124
125 if (cancellable != NULL)
126 {
127 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
128 "Cancellable initialization not supported");
129
130 return FALSE;
131 }
132
133 if (index->file != NULL)
134 {
135 path = g_file_get_path (index->file);
136 }
137
138 if (path == NULL)
139 {
140 g_set_error_literal (error,
141 G_IO_ERROR,
142 G_IO_ERROR_NOT_INITIALIZED,
143 "No file specified");
144 return FALSE;
145 }
146
147 err = git_index_open (&idx, path);
148 g_free (path);
149
150 if (err != GIT_OK)
151 {
152 _ggit_error_set (error, err);
153 return FALSE;
154 }
155
156 _ggit_native_set (initable, idx,
157 (GDestroyNotify) git_index_free);
158
159 return TRUE;
160 }
161
162 static void
ggit_index_initable_iface_init(GInitableIface * iface)163 ggit_index_initable_iface_init (GInitableIface *iface)
164 {
165 iface->init = ggit_index_initable_init;
166 }
167
168 static void
ggit_index_class_init(GgitIndexClass * klass)169 ggit_index_class_init (GgitIndexClass *klass)
170 {
171 GObjectClass *object_class = G_OBJECT_CLASS (klass);
172
173 object_class->dispose = ggit_index_dispose;
174
175 object_class->get_property = ggit_index_get_property;
176 object_class->set_property = ggit_index_set_property;
177
178 g_object_class_install_property (object_class,
179 PROP_FILE,
180 g_param_spec_object ("file",
181 "File",
182 "File",
183 G_TYPE_FILE,
184 G_PARAM_READWRITE |
185 G_PARAM_CONSTRUCT_ONLY |
186 G_PARAM_STATIC_STRINGS));
187 }
188
189 static void
ggit_index_init(GgitIndex * index)190 ggit_index_init (GgitIndex *index)
191 {
192 _ggit_native_set_destroy_func (index, (GDestroyNotify) git_index_free);
193 }
194
195 GgitIndex *
_ggit_index_wrap(git_index * idx)196 _ggit_index_wrap (git_index *idx)
197 {
198 if (idx == NULL)
199 {
200 return NULL;
201 }
202
203 return GGIT_INDEX (g_object_new (GGIT_TYPE_INDEX, "native", idx, NULL));
204 }
205
206 git_index *
_ggit_index_get_index(GgitIndex * idx)207 _ggit_index_get_index (GgitIndex *idx)
208 {
209 g_return_val_if_fail (GGIT_IS_INDEX (idx), NULL);
210
211 return _ggit_native_get (idx);
212 }
213
214 /**
215 * ggit_index_open:
216 * @file: a #GFile.
217 * @error: a #GError for error reporting, or %NULL.
218 *
219 * Create a new bare Git index object as a memory representation of the Git
220 * index file in @file, without a repository to back it.
221 *
222 * Returns: (transfer full) (nullable): a #GgitIndex or %NULL if an error occurred.
223 *
224 **/
225 GgitIndex *
ggit_index_open(GFile * file,GError ** error)226 ggit_index_open (GFile *file,
227 GError **error)
228 {
229 g_return_val_if_fail (G_IS_FILE (file), NULL);
230 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
231
232 return g_initable_new (GGIT_TYPE_INDEX, NULL, error,
233 "file", file,
234 NULL);
235 }
236
237 /**
238 * ggit_index_read:
239 * @idx: a #GgitIndex.
240 * @force: force read even if there are in-memory changes.
241 * @error: a #GError for error reporting, or %NULL.
242 *
243 * Update the contents of an existing index object in memory by reading from
244 * the hard disk.
245 *
246 * If @force is true, this performs a "hard" read that discards in-memory
247 * changes and always reloads the on-disk index data. If there is no on-disk
248 * version, the index will be cleared.
249 *
250 * If @force is false, this does a "soft" read that reloads the index data from
251 * disk only if it has changed since the last time it was loaded. Purely
252 * in-memory index data will be untouched. Be aware: if there are changes on
253 * disk, unwritten in-memory changes are discarded.
254 *
255 * Returns: %TRUE if the index could be read from the file associated with the
256 * index, %FALSE otherwise.
257 *
258 **/
259 gboolean
ggit_index_read(GgitIndex * idx,gboolean force,GError ** error)260 ggit_index_read (GgitIndex *idx,
261 gboolean force,
262 GError **error)
263 {
264 gint ret;
265
266 g_return_val_if_fail (GGIT_IS_INDEX (idx), FALSE);
267 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
268
269 ret = git_index_read (_ggit_native_get (idx), (int)force);
270
271 if (ret != GIT_OK)
272 {
273 _ggit_error_set (error, ret);
274 return FALSE;
275 }
276
277 return TRUE;
278 }
279
280 /**
281 * ggit_index_write:
282 * @idx: a #GgitIndex.
283 * @error: a #GError for error reporting, or %NULL.
284 *
285 * Write an existing index object from memory back to disk using an atomic file
286 * lock.
287 *
288 * Returns: %TRUE if the index was successfully written to disk, %FALSE otherwise.
289 *
290 **/
291 gboolean
ggit_index_write(GgitIndex * idx,GError ** error)292 ggit_index_write (GgitIndex *idx,
293 GError **error)
294 {
295 gint ret;
296
297 g_return_val_if_fail (GGIT_IS_INDEX (idx), FALSE);
298 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
299
300 ret = git_index_write (_ggit_native_get (idx));
301
302 if (ret != GIT_OK)
303 {
304 _ggit_error_set (error, ret);
305 return FALSE;
306 }
307
308 return TRUE;
309 }
310
311 /**
312 * ggit_index_remove:
313 * @idx: a #GgitIndex.
314 * @file: the file to search.
315 * @stage: the stage to search.
316 * @error: a #GError for error reporting, or %NULL.
317 *
318 * Remove a file from the index (specified by position).
319 *
320 * Returns: %TRUE if the file was successfully removed, %FALSE otherwise.
321 *
322 **/
323 gboolean
ggit_index_remove(GgitIndex * idx,GFile * file,gint stage,GError ** error)324 ggit_index_remove (GgitIndex *idx,
325 GFile *file,
326 gint stage,
327 GError **error)
328 {
329 gint ret;
330 gchar *path;
331 GgitRepository *repo;
332 GFile *wd;
333
334 g_return_val_if_fail (GGIT_IS_INDEX (idx), FALSE);
335 g_return_val_if_fail (G_IS_FILE (file), FALSE);
336 g_return_val_if_fail (stage >= 0 && stage <= 3, FALSE);
337 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
338
339 repo = ggit_index_get_owner (idx);
340 wd = ggit_repository_get_workdir (repo);
341 g_object_unref (repo);
342
343 path = g_file_get_relative_path (wd, file);
344 g_object_unref (wd);
345
346 g_return_val_if_fail (path != NULL, FALSE);
347
348 ret = git_index_remove (_ggit_native_get (idx), path, stage);
349 g_free (path);
350
351 if (ret != GIT_OK)
352 {
353 _ggit_error_set (error, ret);
354 return FALSE;
355 }
356
357 return TRUE;
358 }
359
360 /**
361 * ggit_index_add:
362 * @idx: a #GgitIndex.
363 * @entry: a #GgitIndexEntry.
364 * @error: a #GError for error reporting, or %NULL.
365 *
366 * Add a file to the index.
367 *
368 * Returns: %TRUE if the file was successfully added, %FALSE otherwise.
369 *
370 **/
371 gboolean
ggit_index_add(GgitIndex * idx,GgitIndexEntry * entry,GError ** error)372 ggit_index_add (GgitIndex *idx,
373 GgitIndexEntry *entry,
374 GError **error)
375 {
376 gint ret;
377
378 g_return_val_if_fail (GGIT_IS_INDEX (idx), FALSE);
379 g_return_val_if_fail (entry != NULL, FALSE);
380 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
381
382 ret = git_index_add (_ggit_native_get (idx),
383 _ggit_index_entry_get_native (entry));
384
385 if (ret != GIT_OK)
386 {
387 _ggit_error_set (error, ret);
388 return FALSE;
389 }
390
391 return TRUE;
392 }
393
394 /**
395 * ggit_index_get_entries:
396 * @idx: a #GgitIndex.
397 *
398 * Get the index entries enumerator.
399 *
400 * Returns: (transfer full) (nullable): a #GgitIndexEntries or %NULL.
401 *
402 **/
403 GgitIndexEntries *
ggit_index_get_entries(GgitIndex * idx)404 ggit_index_get_entries (GgitIndex *idx)
405 {
406 g_return_val_if_fail (GGIT_IS_INDEX (idx), NULL);
407
408 return _ggit_index_entries_wrap (idx);
409 }
410
411 /**
412 * ggit_index_get_entries_resolve_undo:
413 * @idx: a #GgitIndex.
414 *
415 * Get the resolve undo entries enumerator.
416 *
417 * Returns: (transfer full) (nullable): a #GgitIndexEntriesResolveUndo or %NULL.
418 *
419 **/
420 GgitIndexEntriesResolveUndo *
ggit_index_get_entries_resolve_undo(GgitIndex * idx)421 ggit_index_get_entries_resolve_undo (GgitIndex *idx)
422 {
423 g_return_val_if_fail (GGIT_IS_INDEX (idx), NULL);
424
425 return _ggit_index_entries_resolve_undo_wrap (idx);
426 }
427
428 /**
429 * ggit_index_add_file:
430 * @idx: a #GgitIndex.
431 * @file: file to add.
432 * @error: a #GError for error reporting, or %NULL.
433 *
434 * Add a file to the index. The specified file must be in the working directory
435 * and must exist and be readable.
436 *
437 * Returns: %TRUE if the file was added to the index or %FALSE if there was an error.
438 *
439 **/
440
441 gboolean
ggit_index_add_file(GgitIndex * idx,GFile * file,GError ** error)442 ggit_index_add_file (GgitIndex *idx,
443 GFile *file,
444 GError **error)
445 {
446 GgitRepository *repo;
447 GFile *wd;
448 gchar *path;
449 gint ret;
450
451 g_return_val_if_fail (GGIT_IS_INDEX (idx), FALSE);
452 g_return_val_if_fail (G_IS_FILE (file), FALSE);
453 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
454
455 repo = ggit_index_get_owner (idx);
456
457 wd = ggit_repository_get_workdir (repo);
458 path = g_file_get_relative_path (wd, file);
459
460 g_object_unref (wd);
461 g_object_unref (repo);
462
463 g_return_val_if_fail (path != NULL, FALSE);
464
465 ret = git_index_add_bypath (_ggit_native_get (idx), path);
466 g_free (path);
467
468 if (ret != GIT_OK)
469 {
470 _ggit_error_set (error, ret);
471 return FALSE;
472 }
473
474 return TRUE;
475 }
476
477 /**
478 * ggit_index_add_path:
479 * @idx: a #GgitIndex.
480 * @path: path to the file to add.
481 * @error: a #GError for error reporting, or %NULL.
482 *
483 * Add a file to the index by path. You can specify both relative paths
484 * (to the working directory) and absolute paths. Absolute paths however must
485 * reside in the working directory. The specified path must exist and must be
486 * readable.
487 *
488 * Returns: %TRUE if the file was added to the index or %FALSE if there was an error.
489 *
490 **/
491 gboolean
ggit_index_add_path(GgitIndex * idx,const gchar * path,GError ** error)492 ggit_index_add_path (GgitIndex *idx,
493 const gchar *path,
494 GError **error)
495 {
496 GFile *f;
497 gboolean ret;
498
499 g_return_val_if_fail (GGIT_IS_INDEX (idx), FALSE);
500 g_return_val_if_fail (path != NULL, FALSE);
501 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
502
503 if (!g_path_is_absolute (path))
504 {
505 GgitRepository *repo;
506 GFile *wd;
507
508 repo = ggit_index_get_owner (idx);
509 g_return_val_if_fail (repo != NULL, FALSE);
510
511 wd = ggit_repository_get_workdir (repo);
512 f = g_file_resolve_relative_path (wd, path);
513
514 g_object_unref (wd);
515 g_object_unref (repo);
516 }
517 else
518 {
519 f = g_file_new_for_path (path);
520 }
521
522 ret = ggit_index_add_file (idx, f, error);
523 g_object_unref (f);
524
525 return ret;
526 }
527
528 /**
529 * ggit_index_get_owner:
530 * @idx: a #GgitIndex.
531 *
532 * Get the #GgitRepository that owns the index.
533 *
534 * Returns: (transfer full) (nullable): the #GgitRepository that owns this index or %NULL.
535 *
536 **/
537 GgitRepository *
ggit_index_get_owner(GgitIndex * idx)538 ggit_index_get_owner (GgitIndex *idx)
539 {
540 git_repository *owner;
541
542 g_return_val_if_fail (GGIT_IS_INDEX (idx), NULL);
543
544 owner = git_index_owner (_ggit_native_get (idx));
545 return _ggit_repository_wrap (owner, FALSE);
546 }
547
548 /**
549 * ggit_index_has_conflicts:
550 * @idx: a #GgitIndex.
551 *
552 * Get whether the index has any conflicts.
553 *
554 * Returns: %TRUE if the index has any conflicts, %FALSE otherwise.
555 *
556 **/
557 gboolean
ggit_index_has_conflicts(GgitIndex * idx)558 ggit_index_has_conflicts (GgitIndex *idx)
559 {
560 g_return_val_if_fail (GGIT_IS_INDEX (idx), FALSE);
561
562 return git_index_has_conflicts (_ggit_native_get (idx));
563 }
564
565 /**
566 * ggit_index_write_tree:
567 * @idx: a #GgitIndex.
568 * @error: a #GError for error reporting, or %NULL.
569 *
570 * Write a new tree object to disk containing a representation of the current
571 * state of the index. The index must be associated to an existing repository
572 * and must not contain any files in conflict. You can use the resulting tree
573 * to for instance create a commit.
574 *
575 * Returns: (transfer full) (nullable): a #GgitOId or %NULL in case of an error.
576 *
577 **/
578 GgitOId *
ggit_index_write_tree(GgitIndex * idx,GError ** error)579 ggit_index_write_tree (GgitIndex *idx,
580 GError **error)
581 {
582 git_oid oid;
583 gint ret;
584
585 g_return_val_if_fail (GGIT_IS_INDEX (idx), NULL);
586 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
587
588 ret = git_index_write_tree (&oid, _ggit_native_get (idx));
589
590 if (ret != GIT_OK)
591 {
592 _ggit_error_set (error, ret);
593 return NULL;
594 }
595
596 return _ggit_oid_wrap (&oid);
597 }
598
599 /**
600 * ggit_index_write_tree_to:
601 * @idx: a #GgitIndex.
602 * @repository: a #GgitRepository.
603 * @error: a #GError for error reporting, or %NULL.
604 *
605 * Write a new tree object to @repository containing a representation of the current
606 * state of the index. The index must not contain any files in conflict. You can use
607 * the resulting tree to for instance create a commit.
608 *
609 * Returns: (transfer full) (nullable): a #GgitOId or %NULL in case of an error.
610 *
611 **/
612 GgitOId *
ggit_index_write_tree_to(GgitIndex * idx,GgitRepository * repository,GError ** error)613 ggit_index_write_tree_to (GgitIndex *idx,
614 GgitRepository *repository,
615 GError **error)
616 {
617 git_oid oid;
618 gint ret;
619
620 g_return_val_if_fail (GGIT_IS_INDEX (idx), NULL);
621 g_return_val_if_fail (GGIT_IS_REPOSITORY (repository), NULL);
622 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
623
624 ret = git_index_write_tree_to (&oid,
625 _ggit_native_get (idx),
626 _ggit_native_get (repository));
627
628 if (ret != GIT_OK)
629 {
630 _ggit_error_set (error, ret);
631 return NULL;
632 }
633
634 return _ggit_oid_wrap (&oid);
635 }
636
637 /* ex:set ts=8 noet: */
638