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 		_repo = repo;
56 		_data = Data(tree);
57 	}
58 
59 	@property GitOid id() { return GitOid(*git_tree_id(_data._payload)); }
60 	@property GitRepo owner() { return _repo; }
61 
62 	@property size_t entryCount() { return git_tree_entrycount(_data._payload); }
63 
64 	GitTreeEntry getEntryByName(string filename)
65 	{
66 		auto ret = git_tree_entry_byname(_data._payload, filename.toStringz());
67 		enforce(ret !is null, "Couldn't find tree entry "~filename); // FIXME: use return value
68 		return GitTreeEntry(this, ret);
69 	}
70 
71 	GitTreeEntry getEntryByIndex(size_t index)
72 	{
73 		auto ret = git_tree_entry_byindex(_data._payload, index);
74 		enforce(ret !is null, "Couldn't find tree entry at "~index.to!string); // FIXME: use return value
75 		return GitTreeEntry(this, ret);
76 	}
77 
78 	GitTreeEntry getEntryByOid(GitOid oid)
79 	{
80 		auto ret = git_tree_entry_byoid(_data._payload, &oid._get_oid());
81 		enforce(ret !is null, "Couldn't find tree entry "~oid.toHex()); // FIXME: use return value
82 		return GitTreeEntry(this, ret);
83 	}
84 
85 	GitTreeEntry getEntryByPath(string path)
86 	{
87 		git_tree_entry* ret;
88 		require(git_tree_entry_bypath(&ret, _data._payload, path.toStringz()) == 0);
89 		return GitTreeEntry(ret);
90 	}
91 
92 	void walk(GitTreewalkMode mode, scope GitTreewalkDelegate del)
93 	{
94 		struct CTX { GitTreewalkDelegate del; GitTree tree; }
95 
96 		static extern(C) nothrow int callback(const(char)* root, const(git_tree_entry)* entry, void *payload)
97 		{
98 			auto ctx = cast(CTX*)payload;
99 			try {
100 				final switch (ctx.del(root.to!string(), GitTreeEntry(ctx.tree, entry))) {
101 					case ContinueWalkSkip.yes: return 0;
102 					case ContinueWalkSkip.no: return -1;
103 					case ContinueWalkSkip.skip: return 1;
104 				}
105 			} catch (Exception e) return -1;
106 		}
107 
108 		auto ctx = CTX(del, this);
109 		require(git_tree_walk(_data._payload, cast(git_treewalk_mode)mode, &callback, cast(void*)&ctx) == 0);
110 	}
111 
112     mixin RefCountedGitObject!(git_tree, git_tree_free);
113     // Reference to the parent repository to keep it alive.
114     private GitRepo _repo;
115 }
116 
117 
118 ///
119 enum GitTreewalkMode {
120 	pre = GIT_TREEWALK_PRE,
121 	post = GIT_TREEWALK_POST
122 }
123 
124 ///
125 alias GitTreewalkDelegate = ContinueWalkSkip delegate(string root, GitTreeEntry entry);
126 
127 
128 ///
129 struct GitTreeBuilder {
130 	private this(bool dummy)
131 	{
132 		git_treebuilder* builder;
133 		require(git_treebuilder_create(&builder, null) == 0);
134 		_data = Data(builder);
135 	}
136 
137 	private this(GitTree src)
138 	{
139 		git_treebuilder* builder;
140 		require(git_treebuilder_create(&builder, src._data._payload) == 0);
141 		_data = Data(builder);
142 	}
143 
144 	@property size_t entryCount() { return git_treebuilder_entrycount(_data._payload); }
145 
146 	void clear() { git_treebuilder_clear(_data._payload); }
147 
148 	GitTreeEntry get(string filename)
149 	{
150 		return GitTreeEntry(this, git_treebuilder_get(_data._payload, filename.toStringz()));
151 	}
152 
153 	GitTreeEntry insert(string filename, GitOid id, GitFileModeType filemode)
154 	{
155 		const(git_tree_entry)* ret;
156 		require(git_treebuilder_insert(&ret, _data._payload, filename.toStringz(), &id._get_oid(), cast(git_filemode_t) filemode) == 0);
157 		return GitTreeEntry(this, ret);
158 	}
159 
160 	void remove(string filename)
161 	{
162 		require(git_treebuilder_remove(_data._payload, filename.toStringz()) == 0);
163 	}
164 
165 	// return false from the callback to remove the item
166 	void filter(scope bool delegate(GitTreeEntry entry) del)
167 	{
168 		struct CTX { bool delegate(GitTreeEntry) del; GitTreeBuilder builder; bool exception; }
169 
170 		static extern(C) nothrow int callback(const(git_tree_entry)* entry, void *payload)
171 		{
172 			auto ctx = cast(CTX*)payload;
173 			if (ctx.exception) return 0;
174 			try {
175 				return ctx.del(GitTreeEntry(ctx.builder, entry)) ? 0 : 1;
176 			} catch (Exception e) {
177 				ctx.exception = true;
178 				return 0;
179 			}
180 		}
181 
182 		auto ctx = CTX(del, this, false);
183 		git_treebuilder_filter(_data._payload, &callback, cast(void*)&ctx);
184 	}
185 
186 	GitOid write(GitRepo repo)
187 	{
188 		GitOid ret;
189 		require(git_treebuilder_write(&ret._get_oid(), repo.cHandle, _data._payload) == 0);
190 		return ret;
191 	}
192 
193 
194 	mixin RefCountedGitObject!(git_treebuilder, git_treebuilder_free);
195 }
196 
197 
198 ///
199 struct GitTreeEntry {
200 	package this(GitTree owner, const(git_tree_entry)* entry)
201 	{
202 		_tree = owner;
203 		_entry = entry;
204 	}
205 
206 	package this(GitTreeBuilder owner, const(git_tree_entry)* entry)
207 	{
208 		_builder = owner;
209 		_tree = GitTree.init;
210 		_entry = entry;
211 	}
212 
213 	package this(git_tree_entry* entry)
214 	{
215 		_tree = GitTree.init;
216 		_data = Data(entry);
217 	}
218 
219 	@property string name() { return git_tree_entry_name(cHandle()).to!string(); }
220 	@property GitOid id() { return GitOid(*git_tree_entry_id(cHandle())); }
221 	@property GitType type() { return cast(GitType)git_tree_entry_type(cHandle()); }
222 	@property GitFileModeType fileMode() { return cast(GitFileModeType)git_tree_entry_filemode(cHandle()); }
223 
224 	static if (targetLibGitVersion >= VersionInfo(0, 20, 0))
225 		@property GitFileModeType fileModeRaw() { return cast(GitFileModeType)git_tree_entry_filemode_raw(cHandle()); }
226 
227 	@property GitTreeEntry dup() { return GitTreeEntry(git_tree_entry_dup(cHandle())); }
228 
229 	GitObject toObject(GitRepo repo)
230 	{
231 		git_object* ret;
232 		require(git_tree_entry_to_object(&ret, repo.cHandle, cHandle()) == 0);
233 		return GitObject(repo, ret);
234 	}
235 
236 	int opCmp(GitTreeEntry other) { return git_tree_entry_cmp(cHandle(), other.cHandle()); }
237 	bool opEquals(GitTreeEntry other) { return opCmp(other) == 0; }
238 
239 
240 	package const(git_tree_entry)* cHandle() { return _entry ? _entry : _data._payload; }
241 
242 	mixin RefCountedGitObject!(git_tree_entry, git_tree_entry_free, false);
243 
244 private:
245 	// foreign ownership
246 	GitTreeBuilder _builder;
247 	GitTree _tree;
248 	const(git_tree_entry)* _entry;
249 }