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