1 /*
2  *             Copyright Sönke Ludwig 2014.
3  *  Distributed under the Boost Software License, Version 1.0.
4  *     (See accompanying file LICENSE_1_0.txt or copy at
5  *           http://www.boost.org/LICENSE_1_0.txt)
6  */
7 module git.tree;
8 
9 import git.object_;
10 import git.oid;
11 import git.repository;
12 import git.types;
13 import git.util;
14 import git.version_;
15 
16 import deimos.git2.tree;
17 import deimos.git2.types;
18 
19 import std.conv : to;
20 import std..string : toStringz;
21 import std.exception : enforce;
22 
23 
24 ///
25 GitTree lookupTree(GitRepo repo, GitOid id)
26 {
27 	git_tree* tree;
28 	require(git_tree_lookup(&tree, repo.cHandle, &id._get_oid()) == 0);
29 	return GitTree(repo, tree);
30 }
31 
32 ///
33 GitTree lookupTree(GitRepo repo, GitOid id, size_t id_length)
34 {
35 	git_tree* tree;
36 	require(git_tree_lookup_prefix(&tree, repo.cHandle, &id._get_oid(), id_length) == 0);
37 	return GitTree(repo, tree);
38 }
39 
40 GitTreeBuilder createTreeBuilder()
41 {
42 	return GitTreeBuilder(false);
43 }
44 
45 GitTreeBuilder createTreeBuilder(GitTree source)
46 {
47 	return GitTreeBuilder(source);
48 }
49 
50 
51 ///
52 struct GitTree {
53 	package this(GitRepo repo, git_tree* tree)
54 	{
55 		_object = GitObject(repo, cast(git_object*)tree);
56 	}
57 
58 	this(GitObject object)
59 	{
60 		enforce(object.type == GitType.tree, "GIT object is not a tree.");
61 		_object = object;
62 	}
63 
64 	@property GitOid id() { return GitOid(*git_tree_id(this.cHandle)); }
65 	@property GitRepo owner() { return _object.owner; }
66 
67 	@property size_t entryCount() { return git_tree_entrycount(this.cHandle); }
68 
69 	GitTreeEntry getEntryByName(string filename)
70 	{
71 		auto ret = git_tree_entry_byname(this.cHandle, filename.toStringz());
72 		enforce(ret !is null, "Couldn't find tree entry "~filename); // FIXME: use return value
73 		return GitTreeEntry(this, ret);
74 	}
75 
76 	GitTreeEntry getEntryByIndex(size_t index)
77 	{
78 		auto ret = git_tree_entry_byindex(this.cHandle, index);
79 		enforce(ret !is null, "Couldn't find tree entry at "~index.to!string); // FIXME: use return value
80 		return GitTreeEntry(this, ret);
81 	}
82 
83 	GitTreeEntry getEntryByOid(GitOid oid)
84 	{
85 		auto ret = git_tree_entry_byoid(this.cHandle, &oid._get_oid());
86 		enforce(ret !is null, "Couldn't find tree entry "~oid.toHex()); // FIXME: use return value
87 		return GitTreeEntry(this, ret);
88 	}
89 
90 	GitTreeEntry getEntryByPath(string path)
91 	{
92 		git_tree_entry* ret;
93 		require(git_tree_entry_bypath(&ret, this.cHandle, path.toStringz()) == 0);
94 		return GitTreeEntry(this.owner, ret);
95 	}
96 
97 	void walk(GitTreewalkMode mode, scope GitTreewalkDelegate del)
98 	{
99 		struct CTX { GitTreewalkDelegate del; GitTree tree; }
100 
101 		static extern(C) nothrow int callback(const(char)* root, const(git_tree_entry)* entry, void *payload)
102 		{
103 			auto ctx = cast(CTX*)payload;
104 			try {
105 				final switch (ctx.del(root.to!string(), GitTreeEntry(ctx.tree, entry))) {
106 					case ContinueWalkSkip.yes: return 0;
107 					case ContinueWalkSkip.no: return -1;
108 					case ContinueWalkSkip.skip: return 1;
109 				}
110 			} catch (Exception e) return -1;
111 		}
112 
113 		auto ctx = CTX(del, this);
114 		require(git_tree_walk(this.cHandle, cast(git_treewalk_mode)mode, &callback, cast(void*)&ctx) == 0);
115 	}
116 
117 	package @property inout(git_tree)* cHandle() inout { return cast(inout(git_tree)*)_object.cHandle; }
118 
119 	private GitObject _object;
120 }
121 
122 
123 ///
124 enum GitTreewalkMode {
125 	pre = GIT_TREEWALK_PRE,
126 	post = GIT_TREEWALK_POST
127 }
128 
129 ///
130 alias GitTreewalkDelegate = ContinueWalkSkip delegate(string root, GitTreeEntry entry);
131 
132 
133 ///
134 struct GitTreeBuilder {
135 	private this(bool dummy)
136 	{
137 		git_treebuilder* builder;
138 		require(git_treebuilder_create(&builder, null) == 0);
139 		_data = Data(builder);
140 	}
141 
142 	private this(GitTree src)
143 	{
144 		git_treebuilder* builder;
145 		require(git_treebuilder_create(&builder, src.cHandle) == 0);
146 		_data = Data(builder);
147 	}
148 
149 	@property size_t entryCount() { return git_treebuilder_entrycount(this.cHandle); }
150 
151 	void clear() { git_treebuilder_clear(this.cHandle); }
152 
153 	GitTreeEntry get(string filename)
154 	{
155 		return GitTreeEntry(this, git_treebuilder_get(this.cHandle, filename.toStringz()));
156 	}
157 
158 	GitTreeEntry insert(string filename, GitOid id, GitFileModeType filemode)
159 	{
160 		const(git_tree_entry)* ret;
161 		require(git_treebuilder_insert(&ret, this.cHandle, filename.toStringz(), &id._get_oid(), cast(git_filemode_t) filemode) == 0);
162 		return GitTreeEntry(this, ret);
163 	}
164 
165 	void remove(string filename)
166 	{
167 		require(git_treebuilder_remove(this.cHandle, filename.toStringz()) == 0);
168 	}
169 
170 	// return false from the callback to remove the item
171 	void filter(scope bool delegate(GitTreeEntry entry) del)
172 	{
173 		struct CTX { bool delegate(GitTreeEntry) del; GitTreeBuilder builder; bool exception; }
174 
175 		static extern(C) nothrow int callback(const(git_tree_entry)* entry, void *payload)
176 		{
177 			auto ctx = cast(CTX*)payload;
178 			if (ctx.exception) return 0;
179 			try {
180 				return ctx.del(GitTreeEntry(ctx.builder, entry)) ? 0 : 1;
181 			} catch (Exception e) {
182 				ctx.exception = true;
183 				return 0;
184 			}
185 		}
186 
187 		auto ctx = CTX(del, this, false);
188 		git_treebuilder_filter(this.cHandle, &callback, cast(void*)&ctx);
189 	}
190 
191 	GitOid write(GitRepo repo)
192 	{
193 		GitOid ret;
194 		require(git_treebuilder_write(&ret._get_oid(), repo.cHandle, this.cHandle) == 0);
195 		return ret;
196 	}
197 
198 
199 	mixin RefCountedGitObject!(git_treebuilder, git_treebuilder_free);
200 }
201 
202 
203 ///
204 struct GitTreeEntry {
205 	package this(GitTree owner, const(git_tree_entry)* entry)
206 	{
207 		_tree = owner;
208 		_repo = _tree.owner;
209 		_entry = entry;
210 	}
211 
212 	package this(GitTreeBuilder owner, const(git_tree_entry)* entry)
213 	{
214 		_builder = owner;
215 		_tree = GitTree.init;
216 		_repo = GitRepo.init;
217 		_entry = entry;
218 	}
219 
220 	package this(GitRepo owner, git_tree_entry* entry)
221 	{
222 		_tree = GitTree.init;
223 		_repo = owner;
224 		_data = Data(entry);
225 	}
226 
227 	@property string name() { return git_tree_entry_name(cHandle()).to!string(); }
228 	@property GitOid id() { return GitOid(*git_tree_entry_id(cHandle())); }
229 	@property GitType type() { return cast(GitType)git_tree_entry_type(cHandle()); }
230 	@property GitFileModeType fileMode() { return cast(GitFileModeType)git_tree_entry_filemode(cHandle()); }
231 
232 	static if (targetLibGitVersion >= VersionInfo(0, 20, 0))
233 		@property GitFileModeType fileModeRaw() { return cast(GitFileModeType)git_tree_entry_filemode_raw(cHandle()); }
234 
235 	@property GitTreeEntry dup() { return GitTreeEntry(_repo, git_tree_entry_dup(cHandle())); }
236 
237 	GitObject toObject()
238 	{
239 		git_object* ret;
240 		require(git_tree_entry_to_object(&ret, _repo.cHandle, cHandle()) == 0);
241 		return GitObject(_repo, ret);
242 	}
243 
244 	int opCmp(GitTreeEntry other) { return git_tree_entry_cmp(cHandle(), other.cHandle()); }
245 	bool opEquals(GitTreeEntry other) { return opCmp(other) == 0; }
246 
247 
248 	package const(git_tree_entry)* cHandle() { return _entry ? _entry : _data._payload; }
249 
250 	mixin RefCountedGitObject!(git_tree_entry, git_tree_entry_free, false);
251 
252 private:
253 	// foreign ownership
254 	GitTreeBuilder _builder;
255 	GitTree _tree;
256 	GitRepo _repo;
257 	const(git_tree_entry)* _entry;
258 }