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     ///
132     void connect(GitDirection direction)
133     {
134         require(git_remote_connect(_data._payload, cast(git_direction)direction) == 0);
135     }
136 
137     ///
138     @property bool connected()
139     {
140         return git_remote_connected(_data._payload) != 0;
141     }
142 
143     ///
144     void stop()
145     {
146         git_remote_stop(_data._payload);
147     }
148 
149     ///
150     void disconnect()
151     {
152         git_remote_disconnect(_data._payload);
153     }
154 
155     ///
156     void download(TransferCallbackDelegate progressCallback)
157     {
158         GitRemoteCallbacks cb;
159         cb.transferProgress = progressCallback;
160         download(&cb);
161     }
162     ///
163     void download(GitRemoteCallbacks* callbacks = null)
164     {
165         assert(connected, "Must connect(GitDirection.push) before invoking download().");
166 
167         git_remote_callbacks gitcallbacks;
168         gitcallbacks.progress = &progress_cb;
169         gitcallbacks.completion = &completion_cb;
170         static if (targetLibGitVersion >= VersionInfo(0, 20, 0)) {
171             //gitcallbacks.credentials = &cred_acquire_cb;
172             gitcallbacks.transfer_progress = &transfer_progress_cb;
173         }
174         gitcallbacks.payload = cast(void*)callbacks;
175         require(git_remote_set_callbacks(_data._payload, &gitcallbacks) == 0);
176 
177         static if (targetLibGitVersion == VersionInfo(0, 19, 0)) {
178             require(git_remote_download(_data._payload, &transfer_progress_cb, cast(void*)callbacks) == 0);
179         } else {
180             require(git_remote_download(_data._payload) == 0);
181         }
182     }
183 
184     void addFetch(string refspec) { require(git_remote_add_fetch(this.cHandle, refspec.toStringz) == 0); }
185 
186     void updateTips(scope void delegate(string refname, in ref GitOid a, in ref GitOid b) updateTips)
187     {
188         static struct CTX { GitUpdateTipsDelegate updateTips; }
189 
190         static extern(C) nothrow int update_cb(const(char)* refname, const(git_oid)* a, const(git_oid)* b, void* payload)
191         {
192             auto cbs = cast(CTX*)payload;
193             if (cbs.updateTips) {
194                 try {
195                     auto ac = GitOid(*a);
196                     auto bc = GitOid(*b);
197                     cbs.updateTips(refname.to!string, ac, bc);
198                 } catch (Exception e) return -1;
199             }
200             return 0;
201         }
202 
203         CTX ctx;
204         ctx.updateTips = updateTips;
205 
206         git_remote_callbacks gitcallbacks;
207         gitcallbacks.update_tips = &update_cb;
208         gitcallbacks.payload = &ctx;
209         require(git_remote_set_callbacks(_data._payload, &gitcallbacks) == 0);
210         require(git_remote_update_tips(_data._payload) == 0);
211     }
212 
213     immutable(GitRemoteHead)[] ls()
214     {
215         static if (targetLibGitVersion == VersionInfo(0, 19, 0)) {
216             static struct CTX { immutable(GitRemoteHead)[] heads; }
217 
218             static extern(C) int callback(git_remote_head* rhead, void* payload) {
219                 auto ctx = cast(CTX*)payload;
220                 ctx.heads ~= GitRemoteHead(rhead);
221                 return 0;
222             }
223 
224             CTX ctx;
225             require(git_remote_ls(this.cHandle, &callback, &ctx) == 0);
226             return ctx.heads;
227         } else {
228             const(git_remote_head)** heads;
229             size_t head_count;
230             require(git_remote_ls(&heads, &head_count, _data._payload) == 0);
231             auto ret = new GitRemoteHead[head_count];
232             foreach (i, ref rh; ret) ret[i] = GitRemoteHead(heads[i]);
233             return cast(immutable)ret;
234         }
235     }
236 
237     mixin RefCountedGitObject!(git_remote, git_remote_free);
238     // Reference to the parent repository to keep it alive.
239     private GitRepo _repo;
240 }
241 
242 ///
243 GitRemote createRemote(GitRepo repo, in char[] name, in char[] url)
244 {
245     git_remote* result;
246     require(git_remote_create(&result, repo.cHandle, name.gitStr, url.gitStr) == 0);
247     return GitRemote(repo, result);
248 }
249 
250 ///
251 GitRemote createRemoteInMemory(GitRepo repo, in char[] fetch, in char[] url)
252 {
253     git_remote* result;
254     require(git_remote_create_inmemory(&result, repo.cHandle, fetch.gitStr, url.gitStr) == 0);
255     return GitRemote(repo, result);
256 }
257 
258 ///
259 GitRemote loadRemote(GitRepo repo, in char[] name)
260 {
261     git_remote* result;
262     require(git_remote_load(&result, repo.cHandle, name.gitStr) == 0);
263     return GitRemote(repo, result);
264 }
265 
266 
267 private extern(C) nothrow {
268     static if (targetLibGitVersion == VersionInfo(0, 19, 0)) {
269         void progress_cb(const(char)* str, int len, void* payload)
270         {
271             auto cbs = cast(GitRemoteCallbacks*)payload;
272             if (cbs && cbs.progress) {
273                 try cbs.progress(str[0 .. len].idup);
274                 catch (Exception e) {} // FIXME: store exception and skip calling the callback during the next iterations
275             }
276         }
277     } else {
278         int progress_cb(const(char)* str, int len, void* payload)
279         {
280             auto cbs = cast(GitRemoteCallbacks*)payload;
281             if (cbs && cbs.progress) {
282                 try cbs.progress(str[0 .. len].idup);
283                 catch (Exception e) return -1; // FIXME: store and rethrow exception 
284             }
285             return 0;
286         }
287     }
288 
289     /*int cred_acquire_cb(git_cred** dst, const(char)* url, const(char)* username_from_url, uint allowed_types, void* payload)
290     {
291         auto cbs = cast(GitRemoteCallbacks)payload;
292         try cbs.credentials(...);
293         catch (Exception e) return -1; // FIXME: store and rethrow exception 
294         return 0;
295     }*/
296 
297     int completion_cb(git_remote_completion_type type, void* payload)
298     {
299         auto cbs = cast(GitRemoteCallbacks*)payload;
300         if (cbs && cbs.completion) {
301             try cbs.completion(cast(GitRemoteCompletionType)type);
302             catch (Exception e) return -1; // FIXME: store and rethrow exception 
303         }
304         return 0;
305     }
306 
307     int transfer_progress_cb(const(git_transfer_progress)* stats, void* payload)
308     {
309         auto cbs = cast(GitRemoteCallbacks*)payload;
310         if (cbs && cbs.transferProgress) {
311             try {
312                 auto tp = GitTransferProgress(stats);
313                 cbs.transferProgress(tp);
314             } catch (Exception e) return -1; // FIXME: store and rethrow exception 
315         }
316         return 0;
317     }
318 }
319 
320 /+ TODO: Port these.
321 
322 alias git_remote_rename_problem_cb = int function(const(char)* problematic_refspec, void *payload);
323 
324 int git_remote_get_fetch_refspecs(git_strarray *array, git_remote *remote);
325 
326 int git_remote_add_push(git_remote *remote, const(char)* refspec);
327 
328 int git_remote_get_push_refspecs(git_strarray *array, git_remote *remote);
329 
330 void git_remote_clear_refspecs(git_remote *remote);
331 
332 size_t git_remote_refspec_count(git_remote *remote);
333 
334 const(git_refspec)* git_remote_get_refspec(git_remote *remote, size_t n);
335 
336 int git_remote_remove_refspec(git_remote *remote, size_t n);
337 
338 int git_remote_valid_url(const(char)* url);
339 
340 int git_remote_supported_url(const(char)* url);
341 
342 void git_remote_check_cert(git_remote *remote, int check);
343 
344 void git_remote_set_cred_acquire_cb(
345     git_remote *remote,
346     git_cred_acquire_cb cred_acquire_cb,
347     void *payload);
348 
349 int git_remote_set_transport(
350     git_remote *remote,
351     git_transport *transport);
352 
353 enum git_remote_completion_type {
354     GIT_REMOTE_COMPLETION_DOWNLOAD,
355     GIT_REMOTE_COMPLETION_INDEXING,
356     GIT_REMOTE_COMPLETION_ERROR,
357 } ;
358 
359 mixin _ExportEnumMembers!git_remote_completion_type;
360 
361 struct git_remote_callbacks {
362     uint version_ = GIT_REMOTE_CALLBACKS_VERSION;
363     void function(const(char)* str, int len, void *data) progress;
364     int function(git_remote_completion_type type, void *data) completion;
365     int function(const(char)* refname, const(git_oid)* a, const(git_oid)* b, void *data) update_tips;
366     void *payload;
367 }
368 
369 enum GIT_REMOTE_CALLBACKS_VERSION = 1;
370 enum git_remote_callbacks GIT_REMOTE_CALLBACKS_INIT = { GIT_REMOTE_CALLBACKS_VERSION };
371 
372 int git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks);
373 
374 const(git_transfer_progress)*  git_remote_stats(git_remote *remote);
375 
376 enum git_remote_autotag_option_t {
377     GIT_REMOTE_DOWNLOAD_TAGS_AUTO = 0,
378     GIT_REMOTE_DOWNLOAD_TAGS_NONE = 1,
379     GIT_REMOTE_DOWNLOAD_TAGS_ALL = 2
380 } ;
381 
382 mixin _ExportEnumMembers!git_remote_autotag_option_t;
383 
384 git_remote_autotag_option_t git_remote_autotag(git_remote *remote);
385 
386 void git_remote_set_autotag(
387     git_remote *remote,
388     git_remote_autotag_option_t value);
389 
390 int git_remote_rename(
391     git_remote *remote,
392     const(char)* new_name,
393     git_remote_rename_problem_cb callback,
394     void *payload);
395 
396 int git_remote_update_fetchhead(git_remote *remote);
397 
398 void git_remote_set_update_fetchhead(git_remote *remote, int value);
399 
400 int git_remote_is_valid_name(const(char)* remote_name);
401 
402 +/