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