LCOV - code coverage report
Current view: top level - src/plugins/gitresolver - gitresolver.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 13 436 3.0 %
Date: 2019-09-12 12:28:41 Functions: 4 27 14.8 %

          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             : 

Generated by: LCOV version 1.13