1 /*
2  *  git2r, R bindings to the libgit2 library.
3  *  Copyright (C) 2013-2019 The git2r contributors
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License, version 2,
7  *  as published by the Free Software Foundation.
8  *
9  *  git2r is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License along
15  *  with this program; if not, write to the Free Software Foundation, Inc.,
16  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 #include <git2.h>
20 
21 #include "git2r_arg.h"
22 #include "git2r_deprecated.h"
23 #include "git2r_error.h"
24 #include "git2r_oid.h"
25 #include "git2r_repository.h"
26 #include "git2r_S3.h"
27 
28 /**
29  * Count the number of unique commits between two commit objects
30  *
31  * @param local The commit for local
32  * @param upstream The commit for upstream
33  * @return Integer vector of length two with the values ahead and
34  * behind.
35  */
git2r_graph_ahead_behind(SEXP local,SEXP upstream)36 SEXP git2r_graph_ahead_behind(SEXP local, SEXP upstream)
37 {
38     size_t ahead, behind;
39     int error, nprotect = 0;
40     SEXP result = R_NilValue;
41     SEXP local_repo, local_sha;
42     SEXP upstream_repo, upstream_sha;
43     git_oid local_oid, upstream_oid;
44     git_repository *repository = NULL;
45 
46     if (git2r_arg_check_commit(local))
47         git2r_error(__func__, NULL, "'local'", git2r_err_commit_arg);
48     if (git2r_arg_check_commit(upstream))
49         git2r_error(__func__, NULL, "'upstream'", git2r_err_commit_arg);
50 
51     local_repo = git2r_get_list_element(local, "repo");
52     upstream_repo = git2r_get_list_element(upstream, "repo");
53     if (git2r_arg_check_same_repo(local_repo, upstream_repo))
54         git2r_error(__func__, NULL, "'local' and 'upstream' not from same repository", NULL);
55 
56     repository = git2r_repository_open(local_repo);
57     if (!repository)
58         git2r_error(__func__, NULL, git2r_err_invalid_repository, NULL);
59 
60     local_sha = git2r_get_list_element(local, "sha");
61     git2r_oid_from_sha_sexp(local_sha, &local_oid);
62 
63     upstream_sha = git2r_get_list_element(upstream, "sha");
64     git2r_oid_from_sha_sexp(upstream_sha, &upstream_oid);
65 
66     error = git_graph_ahead_behind(&ahead, &behind, repository, &local_oid,
67                                  &upstream_oid);
68     if (error)
69         goto cleanup;
70 
71     PROTECT(result = Rf_allocVector(INTSXP, 2));
72     nprotect++;
73     INTEGER(result)[0] = ahead;
74     INTEGER(result)[1] = behind;
75 
76 cleanup:
77     git_repository_free(repository);
78 
79     if (nprotect)
80         UNPROTECT(nprotect);
81 
82     if (error)
83         git2r_error(__func__, GIT2R_ERROR_LAST(), NULL, NULL);
84 
85     return result;
86 }
87 
88 /**
89  * Determine if a commit is the descendant of another commit.
90  *
91  * @param commit A commit.
92  * @param ancestor A potential ancestor commit.
93  * @return TRUE or FALSE
94  */
git2r_graph_descendant_of(SEXP commit,SEXP ancestor)95 SEXP git2r_graph_descendant_of(SEXP commit, SEXP ancestor)
96 {
97     int error, descendant_of = 0;
98     SEXP commit_repo, commit_sha;
99     SEXP ancestor_repo, ancestor_sha;
100     git_oid commit_oid, ancestor_oid;
101     git_repository *repository = NULL;
102 
103     if (git2r_arg_check_commit(commit))
104         git2r_error(__func__, NULL, "'commit'", git2r_err_commit_arg);
105     if (git2r_arg_check_commit(ancestor))
106         git2r_error(__func__, NULL, "'ancestor'", git2r_err_commit_arg);
107 
108     commit_repo = git2r_get_list_element(commit, "repo");
109     ancestor_repo = git2r_get_list_element(ancestor, "repo");
110     if (git2r_arg_check_same_repo(commit_repo, ancestor_repo))
111         git2r_error(__func__, NULL, "'commit' and 'ancestor' not from same repository", NULL);
112 
113     repository = git2r_repository_open(commit_repo);
114     if (!repository)
115         git2r_error(__func__, NULL, git2r_err_invalid_repository, NULL);
116 
117     commit_sha = git2r_get_list_element(commit, "sha");
118     git2r_oid_from_sha_sexp(commit_sha, &commit_oid);
119 
120     ancestor_sha = git2r_get_list_element(ancestor, "sha");
121     git2r_oid_from_sha_sexp(ancestor_sha, &ancestor_oid);
122 
123     error = git_graph_descendant_of(repository, &commit_oid, &ancestor_oid);
124     if (0 > error || 1 < error)
125         goto cleanup;
126     descendant_of = error;
127     error = 0;
128 
129 cleanup:
130     git_repository_free(repository);
131 
132     if (error)
133         git2r_error(__func__, GIT2R_ERROR_LAST(), NULL, NULL);
134 
135     return Rf_ScalarLogical(descendant_of);
136 }
137