Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Source for gitresolver plugin
5 : *
6 : * @copyright BSD License (see doc/LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 :
11 : #include <fcntl.h>
12 : #include <git2.h>
13 : #include <kdberrors.h>
14 : #include <kdbhelper.h>
15 : #include <kdbproposal.h>
16 : #include <libgen.h>
17 : #include <openssl/md5.h>
18 : #include <stdio.h>
19 : #include <stdlib.h>
20 : #include <string.h>
21 : #include <sys/stat.h>
22 : #include <sys/time.h>
23 : #include <unistd.h>
24 :
25 : #include "../resolver/shared.h"
26 : #include <kdbinvoke.h>
27 :
28 : #include "gitresolver.h"
29 :
30 : #define TV_MAX_DIGITS 26
31 : #define DEFAULT_CHECKOUT_LOCATION "/tmp/"
32 : #define REFSTRING "refs/heads/"
33 :
34 : typedef enum
35 : {
36 : OBJECT,
37 : HEAD,
38 : } Tracking;
39 :
40 : typedef struct
41 : {
42 : char * tmpFile; // temporary filename for checkout
43 : char * repo; // path to repo (currently only local)
44 : char * workdir; // repository workdir
45 : char * subdirs; // subdirectores between file and workdir
46 : char * branch; // branchname
47 : char * file; // filename
48 : char * refName; // git reference name e.g. refs/heads/master
49 : char * headID; // id of the most recent commit
50 : char * objID; // most recent id of the file
51 : Tracking tracking; // track commit ids or object ids
52 : int setPhase; // Set phase counter, 0 setresolver, 1 commit
53 : mode_t dirmode; //
54 : unsigned char lastHash[MD5_DIGEST_LENGTH]; // hash of the checkdout file
55 : short checkout; // 1 = checkout file to repo, 0 = checkout to temporary file
56 : short pull; // 0 = don't pull repo, 1 = pull repo
57 : } GitData;
58 :
59 :
60 0 : static int elektraResolveFilename (Key * parentKey, ElektraResolveTempfile tmpFile)
61 : {
62 0 : int rc = 0;
63 0 : ElektraInvokeHandle * handle = elektraInvokeOpen ("resolver", 0, 0);
64 0 : if (!handle)
65 : {
66 : rc = -1;
67 : goto RESOLVE_FAILED;
68 : }
69 0 : ElektraResolved * resolved = NULL;
70 : typedef ElektraResolved * (*resolveFileFunc) (elektraNamespace, const char *, ElektraResolveTempfile, Key *);
71 0 : resolveFileFunc resolveFunc = *(resolveFileFunc *) elektraInvokeGetFunction (handle, "filename");
72 :
73 0 : if (!resolveFunc)
74 : {
75 : rc = -1;
76 : goto RESOLVE_FAILED;
77 : }
78 :
79 : typedef void (*freeHandleFunc) (ElektraResolved *);
80 0 : freeHandleFunc freeHandle = *(freeHandleFunc *) elektraInvokeGetFunction (handle, "freeHandle");
81 :
82 0 : if (!freeHandle)
83 : {
84 : rc = -1;
85 : goto RESOLVE_FAILED;
86 : }
87 :
88 0 : resolved = resolveFunc (keyGetNamespace (parentKey), keyString (parentKey), tmpFile, parentKey);
89 :
90 0 : if (!resolved)
91 : {
92 : rc = -1;
93 : goto RESOLVE_FAILED;
94 : }
95 : else
96 : {
97 0 : keySetString (parentKey, resolved->fullPath);
98 0 : freeHandle (resolved);
99 : }
100 :
101 : RESOLVE_FAILED:
102 0 : elektraInvokeClose (handle, 0);
103 0 : return rc;
104 : }
105 0 : int elektraGitresolverCheckFile (const char * filename)
106 : {
107 0 : if (filename[0] == '/') return 0;
108 :
109 0 : return 1;
110 : }
111 :
112 0 : static void genCheckoutFileName (GitData * data)
113 : {
114 : // generate temp filename: /tmp/branch_filename_tv_sec:tv_usec
115 : struct timeval tv;
116 0 : gettimeofday (&tv, 0);
117 0 : const char * fileName = strrchr (data->file, '/');
118 0 : if (!fileName)
119 : fileName = data->file;
120 : else
121 0 : fileName += 1;
122 0 : size_t len = strlen (DEFAULT_CHECKOUT_LOCATION) + strlen (data->branch) + strlen (fileName) + TV_MAX_DIGITS + 1;
123 0 : data->tmpFile = elektraCalloc (len);
124 0 : snprintf (data->tmpFile, len, "%s%s_%s_%lu:" ELEKTRA_TIME_USEC_F, DEFAULT_CHECKOUT_LOCATION, data->branch, fileName, tv.tv_sec,
125 : tv.tv_usec);
126 0 : }
127 :
128 0 : static unsigned char * hashBuffer (const void * buffer, size_t size)
129 : {
130 : MD5_CTX c;
131 0 : unsigned char * out = elektraMalloc (MD5_DIGEST_LENGTH);
132 0 : MD5_Init (&c);
133 0 : MD5_Update (&c, buffer, size);
134 0 : MD5_Final (out, &c);
135 0 : return out;
136 : }
137 :
138 20 : int elektraGitresolverOpen (Plugin * handle ELEKTRA_UNUSED, Key * errorKey ELEKTRA_UNUSED)
139 : {
140 : // plugin initialization logic
141 : // this function is optional
142 20 : return 1; // success
143 : }
144 :
145 20 : int elektraGitresolverClose (Plugin * handle ELEKTRA_UNUSED, Key * errorKey ELEKTRA_UNUSED)
146 : {
147 : // free all plugin resources and shut it down
148 : // this function is optional
149 20 : GitData * data = elektraPluginGetData (handle);
150 20 : if (!data) return 0;
151 0 : if (data->headID) elektraFree (data->headID);
152 0 : if (data->tmpFile)
153 : {
154 0 : unlink (data->tmpFile); // remove temporary checked out file when closing
155 0 : elektraFree (data->tmpFile);
156 : }
157 0 : if (data->repo) elektraFree (data->repo);
158 0 : if (data->workdir) elektraFree (data->workdir);
159 0 : if (data->subdirs) elektraFree (data->subdirs);
160 0 : if (data->file) elektraFree (data->file);
161 0 : if (data->refName) elektraFree (data->refName);
162 0 : elektraFree (data);
163 0 : elektraPluginSetData (handle, NULL);
164 0 : return 1; // success
165 : }
166 0 : static int initData (Plugin * handle, Key * parentKey)
167 : {
168 :
169 0 : GitData * data = elektraPluginGetData (handle);
170 0 : if (!data)
171 : {
172 0 : KeySet * config = elektraPluginGetConfig (handle);
173 0 : data = elektraCalloc (sizeof (GitData));
174 :
175 0 : Key * key = ksLookupByName (config, "/path", KDB_O_NONE);
176 0 : keySetString (parentKey, keyString (key));
177 0 : if (elektraResolveFilename (parentKey, ELEKTRA_RESOLVER_TEMPFILE_NONE) == -1)
178 : {
179 : return -1;
180 : }
181 0 : data->repo = elektraStrDup (keyString (parentKey));
182 :
183 : // default to master branch when no branchname is supplied
184 0 : const char * defaultBranch = "master";
185 0 : key = ksLookupByName (config, "/branch", KDB_O_NONE);
186 0 : if (!key)
187 0 : data->branch = (char *) defaultBranch;
188 : else
189 0 : data->branch = (char *) keyString (key);
190 :
191 0 : key = ksLookupByName (config, "/tracking", KDB_O_NONE);
192 0 : if (!key)
193 0 : data->tracking = HEAD;
194 : else
195 : {
196 0 : if (!strcmp (keyString (key), "object"))
197 0 : data->tracking = OBJECT;
198 : else
199 0 : data->tracking = HEAD;
200 : }
201 0 : key = ksLookupByName (config, "/pull", KDB_O_NONE);
202 0 : if (!key)
203 : {
204 0 : data->pull = 0;
205 : }
206 : else
207 : {
208 0 : data->pull = 1;
209 : }
210 0 : size_t refLen = strlen (REFSTRING) + strlen (data->branch) + 1;
211 0 : data->refName = elektraCalloc (refLen);
212 0 : snprintf (data->refName, refLen, "%s%s", REFSTRING, data->branch);
213 0 : key = ksLookupByName (config, "/checkout", KDB_O_NONE);
214 0 : if (!key)
215 : {
216 0 : data->checkout = 0;
217 : }
218 : else
219 : {
220 0 : data->checkout = 1;
221 : }
222 0 : elektraPluginSetData (handle, data);
223 : }
224 : return 0;
225 : }
226 :
227 0 : static git_buf * discover_repo (char * path)
228 : {
229 0 : if (!strcmp (path, "/")) return NULL;
230 :
231 0 : git_buf * buf = elektraCalloc (sizeof (git_buf));
232 0 : int rc = git_repository_discover (buf, path, 0, NULL);
233 0 : if (rc)
234 : {
235 0 : git_buf_free (buf);
236 0 : elektraFree (buf);
237 0 : buf = discover_repo (dirname (path));
238 0 : return buf;
239 : }
240 : else
241 : {
242 : return buf;
243 : }
244 : }
245 :
246 0 : static git_repository * connectToLocalRepo (GitData * data)
247 : {
248 0 : git_libgit2_init ();
249 : git_repository * repo;
250 : int rc;
251 0 : git_buf buf = GIT_BUF_INIT_CONST (NULL, 0);
252 0 : rc = git_repository_discover (&buf, data->repo, 0, NULL);
253 0 : if (rc)
254 : {
255 0 : char * repoCopy = elektraStrDup (data->repo);
256 0 : git_buf * discovered = discover_repo (dirname (repoCopy));
257 0 : if (discovered)
258 : {
259 0 : if (data->workdir) elektraFree (data->workdir);
260 0 : data->workdir = elektraStrDup (discovered->ptr);
261 0 : elektraFree (repoCopy);
262 0 : git_buf_free (discovered);
263 0 : elektraFree (discovered);
264 : }
265 : else
266 : {
267 0 : git_buf_free (&buf);
268 0 : elektraFree (repoCopy);
269 0 : return NULL;
270 : }
271 : }
272 : else
273 : {
274 0 : if (data->workdir) elektraFree (data->workdir);
275 0 : data->workdir = elektraStrDup (buf.ptr);
276 : }
277 0 : git_buf_free (&buf);
278 0 : rc = git_repository_open_ext (&(repo), data->workdir, 0, NULL);
279 0 : if (rc)
280 : {
281 : return NULL;
282 : }
283 0 : const char * repoPath = git_repository_workdir (repo);
284 0 : if (data->workdir) elektraFree (data->workdir);
285 0 : data->workdir = elektraStrDup (repoPath);
286 : struct stat buffer;
287 0 : if (stat (data->workdir, &buffer) == -1)
288 : {
289 0 : data->dirmode = 0100;
290 : }
291 : else
292 : {
293 0 : data->dirmode = buffer.st_mode;
294 : }
295 0 : char * repoCopy = elektraStrDup (data->repo);
296 0 : char * dir = dirname (repoCopy);
297 0 : if (data->subdirs) elektraFree (data->subdirs);
298 0 : data->subdirs = elektraStrDup (dir + elektraStrLen (data->workdir) - 2);
299 0 : if (!strcmp (data->subdirs, ""))
300 : {
301 0 : elektraFree (data->subdirs);
302 0 : data->subdirs = NULL;
303 : }
304 0 : elektraFree (repoCopy);
305 0 : repoCopy = elektraStrDup (data->repo);
306 0 : if (data->file) elektraFree (data->file);
307 0 : data->file = elektraStrDup (basename (data->repo));
308 0 : elektraFree (repoCopy);
309 0 : return repo;
310 : }
311 :
312 0 : static git_reference * getHeadRef (GitData * data, git_repository * repo)
313 : {
314 : git_reference * headRef;
315 0 : int rc = git_reference_lookup (&headRef, repo, data->refName);
316 0 : if (rc)
317 : {
318 0 : git_reference_free (headRef);
319 : return NULL;
320 : }
321 :
322 : // compare newest commit id to last saved commit id
323 : // only update if there's a newer commit
324 :
325 : // const git_oid * headObj = git_reference_target (headRef);
326 : // git_reference_free (headRef);
327 : // return headObj;
328 0 : return headRef;
329 : }
330 :
331 0 : static char * hasNewCommit (GitData * data, const git_oid * headObj)
332 : {
333 0 : size_t IDSize = GIT_OID_HEXSZ + 1;
334 0 : char * commitID = elektraCalloc (IDSize);
335 0 : git_oid_tostr (commitID, IDSize, headObj);
336 0 : if (!data->headID)
337 : {
338 : return commitID;
339 : }
340 : else
341 : {
342 0 : if (!strcmp (data->headID, commitID))
343 : {
344 0 : elektraFree (commitID);
345 : return NULL;
346 : }
347 : else
348 : {
349 : return commitID;
350 : }
351 : }
352 : }
353 0 : static char * hasNewObjectCommit (GitData * data, git_object * blob)
354 : {
355 0 : size_t IDSize = GIT_OID_HEXSZ + 1;
356 0 : char * objID = elektraCalloc (IDSize);
357 0 : git_oid_tostr (objID, IDSize, git_object_id (blob));
358 0 : if (!data->objID)
359 : {
360 : return objID;
361 : }
362 : else
363 : {
364 0 : if (!strcmp (data->objID, objID))
365 : {
366 0 : elektraFree (objID);
367 : return NULL;
368 : }
369 : else
370 : {
371 : return objID;
372 : }
373 : }
374 : }
375 :
376 :
377 0 : static git_object * getBlob (GitData * data, git_repository * repo)
378 0 : {
379 : git_object * blob;
380 0 : size_t specSize = strlen (data->refName) + strlen (data->file) + 3;
381 0 : if (data->subdirs) specSize += strlen (data->subdirs);
382 0 : char spec[specSize];
383 0 : if (!data->subdirs)
384 : {
385 0 : snprintf (spec, sizeof (spec), "%s:%s", data->refName, data->file);
386 : }
387 : else
388 : {
389 0 : snprintf (spec, sizeof (spec), "%s:%s/%s", data->refName, (data->subdirs) + 1, data->file);
390 : }
391 0 : int rc = git_revparse_single (&blob, repo, spec);
392 0 : if (rc)
393 : {
394 : // file doesn't exist in repo
395 : return NULL;
396 : }
397 0 : return blob;
398 : }
399 :
400 : // inspired by bash make_path
401 0 : static void makePath (GitData * data)
402 : {
403 0 : if (!data->subdirs) return;
404 0 : char * path = elektraStrDup (data->repo);
405 0 : char * ptr = strrchr (path, '/');
406 0 : *ptr = '\0';
407 0 : ptr = path + strlen (data->workdir) - 2;
408 : struct stat sb;
409 0 : while ((ptr = strchr (ptr, '/')) != NULL)
410 : {
411 0 : *ptr = '\0';
412 0 : if (stat (path, &sb) != 0)
413 : {
414 0 : if (mkdir (path, data->dirmode))
415 : {
416 0 : elektraFree (path);
417 0 : return;
418 : }
419 : }
420 0 : else if (S_ISDIR (sb.st_mode) == 0)
421 : {
422 0 : elektraFree (path);
423 0 : return;
424 : }
425 0 : *ptr++ = '/';
426 : }
427 0 : if (stat (path, &sb) && mkdir (path, data->dirmode)) elektraFree (path);
428 : return;
429 : elektraFree (path);
430 : }
431 :
432 : typedef struct
433 : {
434 : char * branchName;
435 : git_oid * oid;
436 : } fetch_cb_data;
437 :
438 0 : static int fetchhead_ref_cb (const char * name, const char * url ELEKTRA_UNUSED, const git_oid * oid, unsigned int is_merge, void * payload)
439 : {
440 0 : if (is_merge)
441 : {
442 0 : fetch_cb_data * data = payload;
443 0 : data->branchName = elektraStrDup (name);
444 0 : data->oid = elektraCalloc (sizeof (git_oid));
445 0 : memcpy (data->oid, oid, sizeof (git_oid));
446 : }
447 0 : return 0;
448 : }
449 :
450 : typedef enum
451 : {
452 : ERROR,
453 : NONE,
454 : NORMAL,
455 : UPTODATE,
456 : FASTFORWARD,
457 : UNBORN,
458 : } MergeAnalysis;
459 :
460 :
461 0 : static MergeAnalysis mergeAnalysis (git_repository * repo, const git_annotated_commit ** heads)
462 : {
463 : git_merge_analysis_t analysis;
464 : git_merge_preference_t preference;
465 0 : int rc = git_merge_analysis (&analysis, &preference, repo, (const git_annotated_commit **) heads, 1);
466 0 : if (rc < 0)
467 : {
468 : return ERROR;
469 : }
470 0 : if (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
471 : {
472 : return FASTFORWARD;
473 : }
474 0 : else if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
475 : {
476 : return UPTODATE;
477 : }
478 0 : else if (analysis & GIT_MERGE_ANALYSIS_NORMAL)
479 : {
480 : return NORMAL;
481 : }
482 : else if (analysis & GIT_MERGE_ANALYSIS_NONE)
483 : {
484 : return NONE;
485 : }
486 0 : else if (analysis & GIT_MERGE_ANALYSIS_UNBORN)
487 : {
488 : return UNBORN;
489 : }
490 : else
491 : {
492 0 : return ERROR;
493 : }
494 : }
495 :
496 0 : static int doMerge (git_repository * repo, const git_annotated_commit ** heads)
497 : {
498 0 : git_merge_options mergeOpts = { GIT_MERGE_OPTIONS_VERSION, .flags = GIT_MERGE_FAIL_ON_CONFLICT };
499 0 : git_checkout_options checkoutOpts = { GIT_CHECKOUT_OPTIONS_VERSION, .checkout_strategy = GIT_CHECKOUT_FORCE };
500 :
501 0 : int rc = git_merge (repo, (const git_annotated_commit **) heads, 1, &mergeOpts, &checkoutOpts);
502 0 : if (rc < 0)
503 : {
504 : return -1;
505 : }
506 0 : return 0;
507 : }
508 :
509 0 : static int setFFTarget (git_repository * repo, git_oid * oid)
510 : {
511 : git_reference * out;
512 : git_reference * ref;
513 0 : git_repository_head (&ref, repo);
514 :
515 0 : int rc = git_reference_set_target (&out, ref, oid, "fastforward");
516 0 : if (rc)
517 : {
518 : return -1;
519 : }
520 0 : return 0;
521 : }
522 :
523 : static int ELEKTRA_UNUSED hasMergeConflicts (git_repository * repo)
524 : {
525 : git_index * cIdx;
526 : int hasConflicts = 0;
527 : git_repository_index (&cIdx, repo);
528 : hasConflicts = git_index_has_conflicts (cIdx);
529 : git_index_free (cIdx);
530 : if (hasConflicts)
531 : {
532 : return -1;
533 : }
534 : return 0;
535 : }
536 :
537 0 : static int pullFromRemote (GitData * data ELEKTRA_UNUSED, git_repository * repo)
538 : {
539 : git_remote * remote;
540 0 : int rc = git_remote_lookup (&remote, repo, "origin");
541 0 : if (rc < 0)
542 : {
543 : return -1;
544 : }
545 0 : rc = git_remote_fetch (remote, NULL, NULL, NULL);
546 0 : if (rc < 0)
547 : {
548 : return -1;
549 : }
550 0 : fetch_cb_data * cb_data = elektraCalloc (sizeof (fetch_cb_data));
551 :
552 0 : git_repository_fetchhead_foreach (repo, fetchhead_ref_cb, cb_data);
553 :
554 : git_annotated_commit * heads[1];
555 0 : rc = git_annotated_commit_lookup (&heads[0], repo, cb_data->oid);
556 0 : if (rc < 0)
557 : {
558 : return -1;
559 : }
560 :
561 0 : MergeAnalysis res = mergeAnalysis (repo, (const git_annotated_commit **) heads);
562 0 : rc = 0;
563 0 : switch (res)
564 : {
565 : case ERROR:
566 : case UNBORN:
567 0 : rc = -1;
568 : goto PULL_CLEANUP;
569 : break;
570 : case NONE:
571 : goto PULL_CLEANUP;
572 : break;
573 : case UPTODATE:
574 : goto PULL_CLEANUP;
575 : break;
576 : case FASTFORWARD:
577 0 : rc = doMerge (repo, (const git_annotated_commit **) heads);
578 0 : if (rc)
579 : {
580 : goto PULL_CLEANUP;
581 : }
582 0 : rc = setFFTarget (repo, cb_data->oid);
583 0 : if (rc)
584 : {
585 : goto PULL_CLEANUP;
586 : }
587 0 : rc = 0;
588 : break;
589 : case NORMAL:
590 : // rc = doMerge(repo, heads);
591 : // if(rc)
592 : //{
593 : // goto PULL_CLEANUP;
594 : //}
595 : // rc = hasMergeConflicts(repo);
596 : // if(rc)
597 : //{
598 : // goto PULL_CLEANUP;
599 : //}
600 0 : rc = -1;
601 : goto PULL_CLEANUP;
602 : break;
603 : default:
604 0 : rc = -1;
605 : goto PULL_CLEANUP;
606 : break;
607 : }
608 :
609 : PULL_CLEANUP:
610 0 : git_annotated_commit_free (heads[0]);
611 0 : git_repository_state_cleanup (repo);
612 :
613 : return rc;
614 : }
615 :
616 20 : int elektraGitresolverGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey)
617 : {
618 20 : if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/gitresolver"))
619 : {
620 20 : KeySet * contract = ksNew (
621 : 30, keyNew ("system/elektra/modules/gitresolver", KEY_VALUE, "gitresolver plugin waits for your orders", KEY_END),
622 : keyNew ("system/elektra/modules/gitresolver/exports", KEY_END),
623 : keyNew ("system/elektra/modules/gitresolver/exports/open", KEY_FUNC, elektraGitresolverOpen, KEY_END),
624 : keyNew ("system/elektra/modules/gitresolver/exports/close", KEY_FUNC, elektraGitresolverClose, KEY_END),
625 : keyNew ("system/elektra/modules/gitresolver/exports/get", KEY_FUNC, elektraGitresolverGet, KEY_END),
626 : keyNew ("system/elektra/modules/gitresolver/exports/set", KEY_FUNC, elektraGitresolverSet, KEY_END),
627 : keyNew ("system/elektra/modules/gitresolver/exports/commit", KEY_FUNC, elektraGitresolverCommit, KEY_END),
628 : keyNew ("system/elektra/modules/gitresolver/exports/error", KEY_FUNC, elektraGitresolverError, KEY_END),
629 : keyNew ("system/elektra/modules/gitresolver/exports/checkfile", KEY_FUNC, elektraGitresolverCheckFile, KEY_END),
630 :
631 : #include ELEKTRA_README
632 : keyNew ("system/elektra/modules/gitresolver/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
633 20 : ksAppend (returned, contract);
634 20 : ksDel (contract);
635 :
636 20 : return 1; // success
637 : }
638 : // get all keys
639 :
640 0 : if (initData (handle, parentKey)) return -1;
641 0 : GitData * data = elektraPluginGetData (handle);
642 0 : git_repository * repo = connectToLocalRepo (data);
643 0 : if (!repo)
644 : {
645 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Failed to open Repository %s\n", data->repo);
646 0 : git_libgit2_shutdown ();
647 0 : return -1;
648 : }
649 :
650 0 : genCheckoutFileName (data);
651 :
652 : // TODO: check for empty repo and initialize repo
653 0 : git_reference * headRef = getHeadRef (data, repo);
654 0 : if (!headRef)
655 : {
656 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Failed to get reference %s\n", data->refName);
657 0 : git_repository_free (repo);
658 0 : git_libgit2_shutdown ();
659 0 : return -1;
660 : }
661 0 : if (data->pull)
662 : {
663 0 : int rc = pullFromRemote (data, repo);
664 0 : if (rc)
665 : {
666 0 : ELEKTRA_SET_CONFLICTING_STATE_ERROR (parentKey, "Fast-forward pull failed, please pull manually\n");
667 0 : git_repository_free (repo);
668 0 : git_libgit2_shutdown ();
669 0 : return -1;
670 : }
671 : }
672 0 : const git_oid * headObj = git_reference_target (headRef);
673 0 : if (!headObj)
674 : {
675 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Failed to get reference %s\n", data->refName);
676 0 : git_reference_free (headRef);
677 0 : git_repository_free (repo);
678 0 : git_libgit2_shutdown ();
679 0 : return -1;
680 : }
681 0 : if (data->tracking == HEAD)
682 : {
683 0 : char * newCommit = hasNewCommit (data, headObj);
684 0 : if (data->headID && !newCommit)
685 : {
686 : // still newest commit, no need to update
687 0 : git_reference_free (headRef);
688 0 : git_repository_free (repo);
689 0 : git_libgit2_shutdown ();
690 0 : return 0;
691 : }
692 0 : else if (data->headID && newCommit)
693 : {
694 0 : elektraFree (data->headID);
695 0 : data->headID = newCommit;
696 : }
697 : else
698 : {
699 0 : data->headID = newCommit;
700 : }
701 0 : elektraPluginSetData (handle, data);
702 0 : data = elektraPluginGetData (handle);
703 : }
704 0 : git_reference_free (headRef);
705 0 : git_object * blob = getBlob (data, repo);
706 0 : if (!blob)
707 : {
708 0 : ELEKTRA_ADD_RESOURCE_WARNINGF (parentKey, "File %s not found in repository %s\n", data->file, data->repo);
709 0 : git_repository_free (repo);
710 0 : git_libgit2_shutdown ();
711 0 : return 0;
712 : }
713 0 : if (data->tracking == OBJECT)
714 : {
715 0 : char * newObj = hasNewObjectCommit (data, blob);
716 0 : if (!newObj)
717 : {
718 0 : git_object_free (blob);
719 0 : git_repository_free (repo);
720 0 : git_libgit2_shutdown ();
721 0 : return 0;
722 : }
723 : else
724 : {
725 0 : if (data->objID)
726 : {
727 0 : elektraFree (data->objID);
728 0 : data->objID = newObj;
729 : }
730 : else
731 : {
732 0 : data->objID = newObj;
733 : }
734 : }
735 0 : elektraPluginSetData (handle, data);
736 0 : data = elektraPluginGetData (handle);
737 : }
738 : FILE * outFile;
739 0 : if (!data->checkout)
740 : {
741 0 : keySetString (parentKey, data->tmpFile);
742 : }
743 : else
744 : {
745 0 : keySetString (parentKey, data->repo);
746 0 : if (data->subdirs) makePath (data);
747 : }
748 0 : outFile = fopen (keyString (parentKey), "w+");
749 0 : if (!outFile)
750 : {
751 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Failed to check out file %s to %s\n", data->file, keyString (parentKey));
752 0 : git_object_free (blob);
753 0 : git_repository_free (repo);
754 0 : git_libgit2_shutdown ();
755 0 : return -1;
756 : }
757 0 : fwrite (git_blob_rawcontent ((git_blob *) blob), (size_t) git_blob_rawsize ((git_blob *) blob), 1, outFile);
758 0 : unsigned char * hash = hashBuffer (git_blob_rawcontent ((git_blob *) blob), git_blob_rawsize ((git_blob *) blob));
759 0 : if (!*(data->lastHash)) memcpy (data->lastHash, hash, MD5_DIGEST_LENGTH);
760 0 : elektraFree (hash);
761 0 : fclose (outFile);
762 0 : git_object_free (blob);
763 0 : git_repository_free (repo);
764 0 : git_libgit2_shutdown ();
765 0 : return 1; // success
766 : }
767 :
768 0 : static git_blob * addFileToIndex (git_repository * repo, GitData * data, git_index * index)
769 : {
770 0 : git_blob * blob = NULL;
771 : git_oid blobID;
772 0 : memset (&blobID, 0, sizeof (git_oid));
773 : git_index_entry ie;
774 0 : memset (&ie, 0, sizeof (git_index_entry));
775 0 : ie.path = data->repo + elektraStrLen (data->workdir) - 1;
776 0 : ie.mode = GIT_FILEMODE_BLOB;
777 0 : git_blob_create_fromdisk (&blobID, repo, data->tmpFile);
778 0 : git_blob_lookup (&blob, repo, &blobID);
779 0 : git_index_add_frombuffer (index, &ie, git_blob_rawcontent (blob), git_blob_rawsize (blob));
780 0 : return blob;
781 : }
782 :
783 0 : static int moveFile (const char * source, const char * dest)
784 : {
785 0 : FILE * inFile = NULL;
786 0 : FILE * outFile = NULL;
787 : struct stat buf;
788 0 : if (stat (source, &buf) == -1) return -1;
789 0 : size_t fileSize = buf.st_size;
790 0 : char * buffer = elektraMalloc (fileSize);
791 0 : inFile = fopen (source, "rb");
792 0 : size_t bytesRead = 0;
793 0 : while (bytesRead < fileSize)
794 : {
795 0 : size_t bytes = fread (buffer + bytesRead, 1, (size_t) fileSize, inFile);
796 0 : if (bytes == 0) break;
797 0 : bytesRead += bytes;
798 : }
799 0 : if (bytesRead < fileSize)
800 : {
801 0 : elektraFree (buffer);
802 0 : fclose (inFile);
803 0 : return -1;
804 : }
805 0 : fclose (inFile);
806 0 : outFile = fopen (dest, "wb+");
807 :
808 0 : size_t bytesWritten = 0;
809 0 : while (bytesWritten < fileSize)
810 : {
811 0 : size_t bytes = fwrite (buffer, 1, fileSize, outFile);
812 0 : if (bytes == 0) break;
813 0 : bytesWritten += bytes;
814 : }
815 0 : fclose (outFile);
816 0 : elektraFree (buffer);
817 :
818 0 : if (bytesWritten < fileSize)
819 : {
820 : return -1;
821 : }
822 0 : unlink (source);
823 0 : return 0;
824 : }
825 :
826 0 : int elektraGitresolverSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
827 : {
828 : // get all keys
829 : // this function is optional
830 0 : GitData * data = elektraPluginGetData (handle);
831 0 : if (!data) return -1;
832 0 : keySetString (parentKey, data->tmpFile);
833 0 : git_repository * repo = connectToLocalRepo (data);
834 0 : if (!repo)
835 : {
836 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Failed to open Repository %s\n", data->repo);
837 0 : git_libgit2_shutdown ();
838 0 : return -1;
839 : }
840 0 : git_reference * headRef = getHeadRef (data, repo);
841 0 : if (!headRef)
842 : {
843 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Failed to get reference %s\n", data->refName);
844 0 : git_repository_free (repo);
845 0 : git_libgit2_shutdown ();
846 0 : return -1;
847 : }
848 0 : const git_oid * headObj = git_reference_target (headRef);
849 0 : if (!headObj)
850 : {
851 0 : ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Failed to get reference %s\n", data->refName);
852 0 : git_reference_free (headRef);
853 0 : git_repository_free (repo);
854 0 : git_libgit2_shutdown ();
855 0 : return -1;
856 : }
857 :
858 0 : if (data->tracking == HEAD)
859 : {
860 0 : char * newCommit = hasNewCommit (data, headObj);
861 0 : if (newCommit)
862 : {
863 : // newer commit in repo - abort
864 0 : ELEKTRA_SET_CONFLICTING_STATE_ERROR (parentKey, "The repository has been updated and is ahead of you");
865 0 : elektraFree (newCommit);
866 0 : git_reference_free (headRef);
867 0 : git_repository_free (repo);
868 0 : git_libgit2_shutdown ();
869 0 : return -1;
870 : }
871 0 : elektraFree (newCommit);
872 : }
873 0 : git_reference_free (headRef);
874 0 : if (data->tracking == OBJECT)
875 : {
876 0 : git_object * blob = getBlob (data, repo);
877 0 : if (blob)
878 : {
879 0 : char * newObj = hasNewObjectCommit (data, blob);
880 0 : if (newObj)
881 : {
882 0 : ELEKTRA_SET_CONFLICTING_STATE_ERROR (parentKey, "The repository has been updated and is ahead of you");
883 0 : elektraFree (newObj);
884 0 : git_object_free (blob);
885 0 : git_repository_free (repo);
886 0 : git_libgit2_shutdown ();
887 0 : return -1;
888 : }
889 0 : git_object_free (blob);
890 : }
891 : }
892 0 : if (!data->setPhase)
893 : {
894 0 : ++(data->setPhase);
895 : }
896 0 : else if (data->setPhase == 1)
897 : {
898 : // get repo index
899 : git_index * index;
900 0 : git_repository_index (&index, repo);
901 :
902 : // add file
903 0 : git_blob * buffer = addFileToIndex (repo, data, index);
904 0 : unsigned char * hash = hashBuffer ((unsigned char *) git_blob_rawcontent (buffer), git_blob_rawsize (buffer));
905 0 : if (!strncmp ((char *) data->lastHash, (char *) hash, MD5_DIGEST_LENGTH))
906 : {
907 0 : elektraFree (hash);
908 0 : git_index_free (index);
909 0 : git_blob_free (buffer);
910 0 : git_repository_free (repo);
911 0 : git_libgit2_shutdown ();
912 0 : return 0;
913 : }
914 0 : elektraFree (hash);
915 :
916 0 : git_index_write (index);
917 :
918 : // get tree id
919 : git_oid treeID;
920 0 : git_index_write_tree (&treeID, index);
921 :
922 : // get parent commit
923 : git_oid parentID;
924 : git_commit * parent;
925 0 : git_reference_name_to_id (&parentID, repo, "HEAD");
926 0 : git_commit_lookup (&parent, repo, &parentID);
927 :
928 : // extract default git user
929 : git_signature * sig;
930 0 : int rc = git_signature_default (&sig, repo);
931 0 : if (rc == GIT_ENOTFOUND)
932 : {
933 0 : git_signature_now (&sig, "Elektra", "@libelektra.org");
934 : }
935 :
936 : // get git tree
937 : git_tree * tree;
938 0 : git_tree_lookup (&tree, repo, &treeID);
939 :
940 : // create default commit
941 : git_oid commitID;
942 0 : git_commit_create (&commitID, repo, "HEAD", sig, sig, NULL, "kdb git autocommit", tree, 1, (const git_commit **) &parent);
943 :
944 :
945 0 : git_signature_free (sig);
946 0 : git_tree_free (tree);
947 0 : git_index_free (index);
948 0 : git_blob_free (buffer);
949 0 : git_commit_free (parent);
950 0 : if (data->checkout)
951 : {
952 0 : moveFile (data->tmpFile, data->repo);
953 : }
954 : }
955 0 : elektraPluginSetData (handle, data);
956 0 : git_repository_free (repo);
957 0 : git_libgit2_shutdown ();
958 0 : return 1; // success
959 : }
960 :
961 0 : int elektraGitresolverError (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
962 : {
963 : // set all keys
964 : // this function is optional
965 :
966 0 : return 1; // success
967 : }
968 :
969 0 : int elektraGitresolverCommit (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA_UNUSED, Key * parentKey ELEKTRA_UNUSED)
970 : {
971 0 : return elektraGitresolverSet (handle, returned, parentKey);
972 : }
973 :
974 20 : Plugin * ELEKTRA_PLUGIN_EXPORT
975 : {
976 : // clang-format off
977 20 : return elektraPluginExport ("gitresolver",
978 : ELEKTRA_PLUGIN_OPEN, &elektraGitresolverOpen,
979 : ELEKTRA_PLUGIN_CLOSE, &elektraGitresolverClose,
980 : ELEKTRA_PLUGIN_GET, &elektraGitresolverGet,
981 : ELEKTRA_PLUGIN_SET, &elektraGitresolverSet,
982 : ELEKTRA_PLUGIN_ERROR, &elektraGitresolverError,
983 : ELEKTRA_PLUGIN_COMMIT, &elektraGitresolverCommit,
984 : ELEKTRA_PLUGIN_END);
985 : }
986 :
|