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