1 /*
2  *             Copyright David Nadlinger 2014.
3  *              Copyright Sönke Ludwig 2014.
4  *  Distributed under the Boost Software License, Version 1.0.
5  *     (See accompanying file LICENSE_1_0.txt or copy at
6  *           http://www.boost.org/LICENSE_1_0.txt)
7  */
8 module git.remote;
9 
10 import git.oid;
11 import git.repository;
12 import git.net;
13 import git.types;
14 import git.util;
15 import git.version_;
16 
17 import deimos.git2.net;
18 import deimos.git2.oid;
19 import deimos.git2.remote;
20 import deimos.git2.strarray;
21 import deimos.git2.types;
22 
23 import std.conv : to;
24 import std.string : toStringz;
25 
26 
27 string[] listRemotes(GitRepo repo)
28 {
29 	git_strarray dst;
30 	require(git_remote_list(&dst, repo.cHandle) == 0);
31 	scope (exit) git_strarray_free(&dst);
32 	auto ret = new string[dst.count];
33 	foreach (i; 0 .. dst.count)
34 		ret[i] = dst.strings[i].to!string();
35 	return ret;
36 }
37 
38 
39 ///
40 enum GitDirection
41 {
42     ///
43     fetch = GIT_DIRECTION_FETCH,
44 
45     ///
46     push = GIT_DIRECTION_PUSH
47 }
48 
49 ///
50 enum GitRemoteAutotagOption
51 {
52     ///
53     automatic = 0,
54 
55     ///
56     none = 1,
57 
58     ///
59     all = 2
60 }
61 
62 ///
63 enum GitRemoteCompletionType {
64     download = GIT_REMOTE_COMPLETION_DOWNLOAD,
65     indexing = GIT_REMOTE_COMPLETION_INDEXING,
66     error = GIT_REMOTE_COMPLETION_ERROR,
67 }
68 
69 ///
70 struct GitRemoteCallbacks {
71     void delegate(string str) progress;
72     void delegate(GitRemoteCompletionType type) completion;
73     //void delegate(GitCred *cred, string url, string username_from_url, uint allowed_types) credentials;
74     TransferCallbackDelegate transferProgress;
75 }
76 
77 alias GitUpdateTipsDelegate = void delegate(string refname, in ref GitOid a, in ref GitOid b);
78 
79 
80 ///
81 struct GitRemote
82 {
83     // Internal, see free-standing constructor functions below.
84     private this(GitRepo repo, git_remote* remote)
85     {
86         _repo = repo;
87         _data = Data(remote);
88     }
89 
90     ///
91     void save()
92     {
93         require(git_remote_save(_data._payload) == 0);
94     }
95 
96     ///
97     @property string name() const
98     {
99         return to!string(git_remote_name(_data._payload));
100     }
101 
102     ///
103     @property string url() const
104     {
105         return to!string(git_remote_url(_data._payload));
106     }
107 
108     ///
109     @property void name(in char[] url)
110     {
111         require(git_remote_set_url(_data._payload, url.gitStr) == 0);
112     }
113 
114     ///
115     @property string pushURL() const
116     {
117         return to!string(git_remote_pushurl(_data._payload));
118     }
119 
120     ///
121     @property void pushURL(in char[] url)
122     {
123         require(git_remote_set_pushurl(_data._payload, url.gitStr) == 0);
124     }
125 
126     @property GitTransferProgress stats()
127     {
128         return GitTransferProgress(git_remote_stats(_data._payload));
129     }
130 
131 	@property GitRemoteAutotag autoTag() { return cast(GitRemoteAutotag)git_remote_autotag(this.cHandle); }
132 	@property void autoTag(GitRemoteAutotag value) { git_remote_set_autotag(this.cHandle, cast(git_remote_autotag_option_t)value); }
133 
134     void connect(GitDirection direction)
135     {
136         require(git_remote_connect(_data._payload, cast(git_direction)direction) == 0);
137     }
138 
139     ///
140     @property bool connected()
141     {
142         return git_remote_connected(_data._payload) != 0;
143     }
144 
145     ///
146     void stop()
147     {
148         git_remote_stop(_data._payload);
149     }
150 
151     ///
152     void disconnect()
153     {
154         git_remote_disconnect(_data._payload);
155     }
156 
157     ///
158     void download(TransferCallbackDelegate progressCallback)
159     {
160         GitRemoteCallbacks cb;
161         cb.transferProgress = progressCallback;
162         download(&cb);
163     }
164     ///
165     void download(GitRemoteCallbacks* callbacks = null)
166     {
167         assert(connected, "Must connect(GitDirection.push) before invoking download().");
168 
169         git_remote_callbacks gitcallbacks;
170         gitcallbacks.progress = &progress_cb;
171         gitcallbacks.completion = &completion_cb;
172         static if (targetLibGitVersion >= VersionInfo(0, 20, 0)) {
173             //gitcallbacks.credentials = &cred_acquire_cb;
174             gitcallbacks.transfer_progress = &transfer_progress_cb;
175         }
176         gitcallbacks.payload = cast(void*)callbacks;
177         require(git_remote_set_callbacks(_data._payload, &gitcallbacks) == 0);
178 
179         static if (targetLibGitVersion == VersionInfo(0, 19, 0)) {
180             require(git_remote_download(_data._payload, &transfer_progress_cb, cast(void*)callbacks) == 0);
181         } else {
182             require(git_remote_download(_data._payload) == 0);
183         }
184     }
185 
186     void addFetch(string refspec) { require(git_remote_add_fetch(this.cHandle, refspec.toStringz) == 0); }
187 
188     void updateTips(scope void delegate(string refname, in ref GitOid a, in ref GitOid b) updateTips)
189     {
190         static struct CTX { GitUpdateTipsDelegate updateTips; Exception e; }
191 
192         static extern(C) nothrow int update_cb(const(char)* refname, const(git_oid)* a, const(git_oid)* b, void* payload)
193         {
194             auto ctx = cast(CTX*)payload;
195             if (ctx.updateTips) {
196                 try {
197                     auto ac = GitOid(*a);
198                     auto bc = GitOid(*b);
199                     ctx.updateTips(refname.to!string, ac, bc);
200                 } catch (Exception e) {
201                     ctx.e = e;
202                     return -1;
203                 }
204             }
205             return 0;
206         }
207 
208         CTX ctx;
209         ctx.updateTips = updateTips;
210 
211         git_remote_callbacks gitcallbacks;
212         gitcallbacks.update_tips = &update_cb;
213         gitcallbacks.payload = &ctx;
214         require(git_remote_set_callbacks(_data._payload, &gitcallbacks) == 0);
215         auto ret = git_remote_update_tips(_data._payload);
216         if (ctx.e) throw ctx.e;
217         require(ret == 0);
218     }
219 
220     immutable(GitRemoteHead)[] ls()
221     {
222         static if (targetLibGitVersion == VersionInfo(0, 19, 0)) {
223             static struct CTX { immutable(GitRemoteHead)[] heads; }
224 
225             static extern(C) int callback(git_remote_head* rhead, void* payload) {
226                 auto ctx = cast(CTX*)payload;
227                 ctx.heads ~= GitRemoteHead(rhead);
228                 return 0;
229             }
230 
231             CTX ctx;
232             require(git_remote_ls(this.cHandle, &callback, &ctx) == 0);
233             return ctx.heads;
234         } else {
235             const(git_remote_head)** heads;
236             size_t head_count;
237             require(git_remote_ls(&heads, &head_count, _data._payload) == 0);
238             auto ret = new GitRemoteHead[head_count];
239             foreach (i, ref rh; ret) ret[i] = GitRemoteHead(heads[i]);
240             return cast(immutable)ret;
241         }
242     }
243 
244     mixin RefCountedGitObject!(git_remote, git_remote_free);
245     // Reference to the parent repository to keep it alive.
246     private GitRepo _repo;
247 }
248 
249 ///
250 GitRemote createRemote(GitRepo repo, in char[] name, in char[] url)
251 {
252     git_remote* result;
253     require(git_remote_create(&result, repo.cHandle, name.gitStr, url.gitStr) == 0);
254     return GitRemote(repo, result);
255 }
256 
257 ///
258 GitRemote createRemoteInMemory(GitRepo repo, in char[] fetch, in char[] url)
259 {
260     git_remote* result;
261     require(git_remote_create_inmemory(&result, repo.cHandle, fetch.gitStr, url.gitStr) == 0);
262     return GitRemote(repo, result);
263 }
264 
265 ///
266 GitRemote loadRemote(GitRepo repo, in char[] name)
267 {
268     git_remote* result;
269     require(git_remote_load(&result, repo.cHandle, name.gitStr) == 0);
270     return GitRemote(repo, result);
271 }
272 
273 
274 private extern(C) nothrow {
275     static if (targetLibGitVersion == VersionInfo(0, 19, 0)) {
276         void progress_cb(const(char)* str, int len, void* payload)
277         {
278             auto cbs = cast(GitRemoteCallbacks*)payload;
279             if (cbs && cbs.progress) {
280                 try cbs.progress(str[0 .. len].idup);
281                 catch (Exception e) {} // FIXME: store exception and skip calling the callback during the next iterations
282             }
283         }
284     } else {
285         int progress_cb(const(char)* str, int len, void* payload)
286         {
287             auto cbs = cast(GitRemoteCallbacks*)payload;
288             if (cbs && cbs.progress) {
289                 try cbs.progress(str[0 .. len].idup);
290                 catch (Exception e) return -1; // FIXME: store and rethrow exception 
291             }
292             return 0;
293         }
294     }
295 
296     /*int cred_acquire_cb(git_cred** dst, const(char)* url, const(char)* username_from_url, uint allowed_types, void* payload)
297     {
298         auto cbs = cast(GitRemoteCallbacks)payload;
299         try cbs.credentials(...);
300         catch (Exception e) return -1; // FIXME: store and rethrow exception 
301         return 0;
302     }*/
303 
304     int completion_cb(git_remote_completion_type type, void* payload)
305     {
306         auto cbs = cast(GitRemoteCallbacks*)payload;
307         if (cbs && cbs.completion) {
308             try cbs.completion(cast(GitRemoteCompletionType)type);
309             catch (Exception e) return -1; // FIXME: store and rethrow exception 
310         }
311         return 0;
312     }
313 
314     int transfer_progress_cb(const(git_transfer_progress)* stats, void* payload)
315     {
316         auto cbs = cast(GitRemoteCallbacks*)payload;
317         if (cbs && cbs.transferProgress) {
318             try {
319                 auto tp = GitTransferProgress(stats);
320                 cbs.transferProgress(tp);
321             } catch (Exception e) return -1; // FIXME: store and rethrow exception 
322         }
323         return 0;
324     }
325 }
326 
327 enum GitRemoteAutotag {
328 	auto_ = GIT_REMOTE_DOWNLOAD_TAGS_AUTO,
329 	none = GIT_REMOTE_DOWNLOAD_TAGS_NONE,
330 	all = GIT_REMOTE_DOWNLOAD_TAGS_ALL
331 }
332 
333 
334 /+ TODO: Port these.
335 
336 alias git_remote_rename_problem_cb = int function(const(char)* problematic_refspec, void *payload);
337 
338 int git_remote_get_fetch_refspecs(git_strarray *array, git_remote *remote);
339 
340 int git_remote_add_push(git_remote *remote, const(char)* refspec);
341 
342 int git_remote_get_push_refspecs(git_strarray *array, git_remote *remote);
343 
344 void git_remote_clear_refspecs(git_remote *remote);
345 
346 size_t git_remote_refspec_count(git_remote *remote);
347 
348 const(git_refspec)* git_remote_get_refspec(git_remote *remote, size_t n);
349 
350 int git_remote_remove_refspec(git_remote *remote, size_t n);
351 
352 int git_remote_valid_url(const(char)* url);
353 
354 int git_remote_supported_url(const(char)* url);
355 
356 void git_remote_check_cert(git_remote *remote, int check);
357 
358 void git_remote_set_cred_acquire_cb(
359     git_remote *remote,
360     git_cred_acquire_cb cred_acquire_cb,
361     void *payload);
362 
363 int git_remote_set_transport(
364     git_remote *remote,
365     git_transport *transport);
366 
367 enum git_remote_completion_type {
368     GIT_REMOTE_COMPLETION_DOWNLOAD,
369     GIT_REMOTE_COMPLETION_INDEXING,
370     GIT_REMOTE_COMPLETION_ERROR,
371 } ;
372 
373 mixin _ExportEnumMembers!git_remote_completion_type;
374 
375 struct git_remote_callbacks {
376     uint version_ = GIT_REMOTE_CALLBACKS_VERSION;
377     void function(const(char)* str, int len, void *data) progress;
378     int function(git_remote_completion_type type, void *data) completion;
379     int function(const(char)* refname, const(git_oid)* a, const(git_oid)* b, void *data) update_tips;
380     void *payload;
381 }
382 
383 enum GIT_REMOTE_CALLBACKS_VERSION = 1;
384 enum git_remote_callbacks GIT_REMOTE_CALLBACKS_INIT = { GIT_REMOTE_CALLBACKS_VERSION };
385 
386 int git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks);
387 
388 const(git_transfer_progress)*  git_remote_stats(git_remote *remote);
389 
390 
391 
392 int git_remote_rename(
393     git_remote *remote,
394     const(char)* new_name,
395     git_remote_rename_problem_cb callback,
396     void *payload);
397 
398 int git_remote_update_fetchhead(git_remote *remote);
399 
400 void git_remote_set_update_fetchhead(git_remote *remote, int value);
401 
402 int git_remote_is_valid_name(const(char)* remote_name);
403 
404 +/