chigraph  master
Systems programming language written for beginners in LLVM
Fetcher.cpp
2 
3 #include <git2.h>
4 
5 #include <boost/filesystem/fstream.hpp>
6 #include <boost/filesystem/operations.hpp>
7 
8 namespace fs = boost::filesystem;
9 
10 namespace chi {
11 
12 Result fetchModule(const fs::path& workspacePath, const fs::path& name, bool recursive) {
13  // init it (pretty sure it inits windows networking stuff)
14  git_libgit2_init();
15 
16  Result res;
17 
18  auto modCtx = res.addScopedContext({{"Module Name", name.string()}});
19 
20  if (name == "lang") { return res; }
21 
22  // get the url
23  std::string url;
24  std::string cloneInto;
25  VCSType type;
26  std::tie(type, url, cloneInto) = resolveUrlFromModuleName(name);
27 
28  // see if it exists
29  auto fileName = workspacePath / "src" / fs::path(name).replace_extension(".chimod");
30  bool exists = fs::is_regular_file(fileName);
31 
32  if (exists) {
33  // try to pull it
34 
35  // see if it's actually a git repository
36  auto repoPath = workspacePath / "src" / cloneInto;
37  if (cloneInto == "" || !fs::is_directory(repoPath / ".git")) {
38  // it's not a git repo, just exit
39 
40  return res;
41  }
42 
43  auto repoPathCtx = res.addScopedContext({{"Repo Path", repoPath.string()}});
44 
45  if (type == VCSType::Unknown) {
46  res.addEntry("EUKN", "Could not resolve URL for module", {});
47  return res;
48  }
49  assert(type == VCSType::Git && "Currently only Git is implemented for fetching modules.");
50 
51  // open the repository
52  git_repository* repo;
53  int err = git_repository_open(&repo, repoPath.string().c_str());
54  if (err != 0) {
55  res.addEntry("EUKN", "Failed to open git repository",
56  {{"Error Message", giterr_last()->message}});
57  return res;
58  }
59 
60  // get the remote
61  git_remote* origin;
62  err = git_remote_lookup(&origin, repo, "origin");
63  if (err != 0) {
64  res.addEntry("EUKN", "Failed to get remote origin",
65  {{"Error Message", giterr_last()->message}});
66  return res;
67  }
68 
69  // fetch
70  git_fetch_options opts = GIT_FETCH_OPTIONS_INIT;
71  err = git_remote_fetch(origin, nullptr, &opts, nullptr);
72  if (err != 0) {
73  res.addEntry("EUKN", "Failed to fetch repo",
74  {{"Error Message", giterr_last()->message}});
75  return res;
76  }
77 
78  // get which heads we need to merge
79  std::pair<std::string, git_oid> oid_to_merge;
80  git_repository_fetchhead_foreach(
81  repo,
82  [](const char* name, const char* /*url*/, const git_oid* oid, unsigned int is_merge,
83  void* payload) -> int {
84  auto& oids_to_merge = *reinterpret_cast<std::pair<std::string, git_oid>*>(payload);
85 
86  if (is_merge != 0u) { oids_to_merge = {name, *oid}; }
87 
88  return 0;
89 
90  },
91  &oid_to_merge);
92 
93  // get origin/master
94  git_annotated_commit* originmaster;
95  err = git_annotated_commit_lookup(&originmaster, repo, &oid_to_merge.second);
96  if (err != 0) {
97  res.addEntry("EUKN", "Failed to get new head from repo",
98  {{"Error Message", giterr_last()->message}});
99  return res;
100  }
101 
102  auto annotatedCommits = const_cast<const git_annotated_commit**>(&originmaster);
103 
104  // see what we need to do
105  git_merge_analysis_t anaylisis;
106  git_merge_preference_t pref;
107  git_merge_analysis(&anaylisis, &pref, repo, annotatedCommits, 1);
108 
109  if ((anaylisis & GIT_MERGE_ANALYSIS_UP_TO_DATE) != 0 ||
110  (anaylisis & GIT_MERGE_ANALYSIS_NONE) != 0) {
111  // nothing to do, just return
112  return res;
113  }
114 
115  if ((anaylisis & GIT_MERGE_ANALYSIS_FASTFORWARD) != 0) {
116  // we can fast forward, do it
117 
118  // get master
119  git_reference* master;
120  err = git_repository_head(&master, repo);
121 
122  if (err != 0) {
123  res.addEntry("EUKN", "Failed to get reference to master",
124  {{"Error Message", giterr_last()->message}});
125  return res;
126  }
127 
128  // fast forward
129  git_reference* createdRef;
130  err = git_reference_set_target(&createdRef, master, &oid_to_merge.second, "pull");
131  if (err != 0) {
132  res.addEntry("EUKN", "Failed to fast forward",
133  {{"Error Message", giterr_last()->message}});
134  return res;
135  }
136 
137  // get head
138  git_index* head;
139  err = git_repository_index(&head, repo);
140  if (err != 0) {
141  res.addEntry("EUKN", "Failed to get HEAD",
142  {{"Error Message", giterr_last()->message}});
143  return res;
144  }
145 
146  // reset to it
147  git_oid oid{};
148  err = git_index_write_tree_to(&oid, head, repo);
149  if (err != 0) {
150  res.addEntry("EUKN", "Failed to write index to tree",
151  {{"Error Message", giterr_last()->message}});
152  return res;
153  }
154 
155  } else if ((anaylisis & GIT_MERGE_ANALYSIS_NORMAL) != 0) {
156  // merge and commit
157  git_merge_options mergeOpts = GIT_MERGE_OPTIONS_INIT;
158  git_checkout_options checkoutOpts = GIT_CHECKOUT_OPTIONS_INIT;
159  checkoutOpts.checkout_strategy = GIT_CHECKOUT_SAFE; // see
160  // http://stackoverflow.com/questions/39651287/doing-a-git-pull-with-libgit2
161  err = git_merge(repo, annotatedCommits, 1, &mergeOpts, &checkoutOpts);
162  if (err != 0) {
163  res.addEntry("EUKN", "Failed to merge branch",
164  {{"Error Message", giterr_last()->message}});
165  return res;
166  }
167 
168  // see if there are conflicts
169 
170  // get head
171  git_index* head;
172  err = git_repository_index(&head, repo);
173  if (err != 0) {
174  res.addEntry("EUKN", "Failed to get HEAD",
175  {{"Error Message", giterr_last()->message}});
176  return res;
177  }
178 
179  // check for conflicts
180  if (git_index_has_conflicts(head) != 0) {
181  // there are conflicts
182  res.addEntry("WUKN", "Merge conflicts when pulling, manually resolve them.", {});
183  return res;
184  }
185 
186  // commit the merge
187 
188  // create a signature for this code
189  git_signature* committerSignature;
190  err = git_signature_now(&committerSignature, "Chigraph Fetch",
192  if (err != 0) {
193  res.addEntry("EUKN", "Failed to create git signature",
194  {{"Error Message", giterr_last()->message}});
195  return res;
196  }
197 
198  // get the origin/master commit
199  git_commit* origin_master_commit;
200  err = git_commit_lookup(&origin_master_commit, repo, &oid_to_merge.second);
201  if (err != 0) {
202  res.addEntry("EUKN", "Failed to get commit for origin/master",
203  {{"Error Message", giterr_last()->message}});
204  return res;
205  }
206 
207  // get the head commit
208  git_oid parent_headoid{};
209  err = git_reference_name_to_id(&parent_headoid, repo, "HEAD");
210  if (err != 0) {
211  res.addEntry("EUKN", "Failed to get reference to HEAD",
212  {{"Error Message", giterr_last()->message}});
213  return res;
214  }
215 
216  git_commit* head_parent;
217  err = git_commit_lookup(&head_parent, repo, &parent_headoid);
218  if (err != 0) {
219  res.addEntry("EUKN", "Failed to get commit from oid",
220  {{"Error Message", giterr_last()->message}});
221  return res;
222  }
223 
224  // get the tree
225  git_tree* tree;
226  err = git_commit_tree(&tree, head_parent);
227  if (err != 0) {
228  res.addEntry("EUKN", "Failed to git tree from commit",
229  {{"Error Message", giterr_last()->message}});
230  }
231 
232  const git_commit* parents[] = {head_parent, origin_master_commit};
233 
234  git_oid newCommit{};
235  std::string commitMsg = std::string("Merge ") + git_oid_tostr_s(&oid_to_merge.second);
236  err = git_commit_create(&newCommit, repo, "HEAD", committerSignature,
237  committerSignature, "UTF-8", commitMsg.c_str(), tree,
238  sizeof(parents) / sizeof(git_commit*),
239  static_cast<const git_commit**>(parents));
240  if (err != 0) {
241  res.addEntry("EUKN", "Failed to create commit",
242  {{"Error Message", giterr_last()->message}});
243  }
244  }
245 
246  git_annotated_commit_free(originmaster);
247  git_repository_state_cleanup(repo);
248 
249  } else {
250  // doesn't exist, clone
251 
252  if (type == VCSType::Unknown) {
253  res.addEntry("EUKN", "Could not resolve URL for module", {});
254  return res;
255  }
256  assert(type == VCSType::Git);
257 
258  auto absCloneInto = workspacePath / "src" / cloneInto;
259  // make sure the directory exists
260  fs::create_directories(absCloneInto.parent_path());
261 
262  // clone it
263  git_repository* repo;
264  int err = git_clone(&repo, url.c_str(), absCloneInto.string().c_str(), nullptr);
265 
266  // check for error
267  if (err != 0) {
268  res.addEntry("EUKN", "Failed to clone repository",
269  {{"Error Code", err}, {"Error Message", giterr_last()->message}});
270  return res;
271  }
272  }
273 
274  if (!fs::is_regular_file(fileName)) {
275  res.addEntry("EUKN", "Module doesn't exist", {{"File Name", fileName.string()}});
276  return res;
277  }
278 
279  if (recursive) {
280  // peek at the dependencies
281  // TODO(#79): is there a cleaner way to do this?
282  nlohmann::json j;
283  try {
284  fs::ifstream file{fileName};
285  file >> j;
286  } catch (std::exception& e) {
287  res.addEntry("EUKN", "Failed to parse JSON",
288  {{"File", fileName.string()}, {"Error Message", e.what()}});
289  }
290 
291  if (j.find("dependencies") != j.end() || !j["dependencies"].is_array()) { return res; }
292 
293  // fetch the dependencies
294  for (const auto& dep : j["dependencies"]) {
295  std::string depName = dep;
296  fetchModule(workspacePath, depName, true);
297  }
298  }
299 
300  return res;
301 }
302 
303 std::tuple<VCSType, std::string, std::string> resolveUrlFromModuleName(
304  const boost::filesystem::path& path) {
305  // handle github
306  {
307  auto beginIter = path.begin();
308  if (beginIter != path.end() && *beginIter == "github.com") {
309  std::string folderName = beginIter->string();
310 
311  // get the url
312  ++beginIter;
313  if (beginIter != path.end()) {
314  folderName += "/";
315  folderName += beginIter->string();
316  ++beginIter;
317  if (beginIter != path.end()) {
318  folderName += "/";
319  folderName += beginIter->string();
320  }
321  return std::make_tuple(VCSType::Git, "https://" + folderName, folderName);
322  }
323  }
324  }
325  return std::make_tuple(VCSType::Unknown, "", "");
326 }
327 
328 } // namespace chi
Result fetchModule(const boost::filesystem::path &workspacePath, const boost::filesystem::path &name, bool recursive)
Downloads a module from a remote URL, currently supports.
The namespace where chigraph lives.
std::tuple< VCSType, std::string, std::string > resolveUrlFromModuleName(const boost::filesystem::path &path)
Get the URL for a VCS repository from a module name.
Definition: Fetcher.cpp:303