]> git.proxmox.com Git - libgit2.git/blob - src/reflog.c
Update Copyright header
[libgit2.git] / src / reflog.c
1 /*
2 * Copyright (C) 2009-2012 the libgit2 contributors
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
8 #include "reflog.h"
9 #include "repository.h"
10 #include "filebuf.h"
11 #include "signature.h"
12
13 static int reflog_init(git_reflog **reflog, git_reference *ref)
14 {
15 git_reflog *log;
16
17 *reflog = NULL;
18
19 log = git__malloc(sizeof(git_reflog));
20 if (log == NULL)
21 return GIT_ENOMEM;
22
23 memset(log, 0x0, sizeof(git_reflog));
24
25 log->ref_name = git__strdup(ref->name);
26
27 if (git_vector_init(&log->entries, 0, NULL) < 0) {
28 git__free(log->ref_name);
29 git__free(log);
30 return GIT_ENOMEM;
31 }
32
33 *reflog = log;
34
35 return GIT_SUCCESS;
36 }
37
38 static int reflog_write(const char *log_path, const char *oid_old,
39 const char *oid_new, const git_signature *committer,
40 const char *msg)
41 {
42 int error;
43 git_buf log = GIT_BUF_INIT;
44 git_filebuf fbuf = GIT_FILEBUF_INIT;
45
46 assert(log_path && oid_old && oid_new && committer);
47
48 git_buf_puts(&log, oid_old);
49 git_buf_putc(&log, ' ');
50
51 git_buf_puts(&log, oid_new);
52
53 git_signature__writebuf(&log, " ", committer);
54 git_buf_truncate(&log, log.size - 1); /* drop LF */
55
56 if (msg) {
57 if (strchr(msg, '\n')) {
58 git_buf_free(&log);
59 return git__throw(GIT_ERROR, "Reflog message cannot contain newline");
60 }
61
62 git_buf_putc(&log, '\t');
63 git_buf_puts(&log, msg);
64 }
65
66 git_buf_putc(&log, '\n');
67
68 if ((error = git_buf_lasterror(&log)) < GIT_SUCCESS) {
69 git_buf_free(&log);
70 return git__rethrow(error, "Failed to write reflog. Memory allocation failure");
71 }
72
73 if ((error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND)) < GIT_SUCCESS) {
74 git_buf_free(&log);
75 return git__rethrow(error, "Failed to write reflog. Cannot open reflog `%s`", log_path);
76 }
77
78 git_filebuf_write(&fbuf, log.ptr, log.size);
79 error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE);
80
81 git_buf_free(&log);
82
83 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write reflog");
84 }
85
86 static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
87 {
88 int error = GIT_SUCCESS;
89 const char *ptr;
90 git_reflog_entry *entry;
91
92 #define seek_forward(_increase) { \
93 if (_increase >= buf_size) { \
94 if (entry->committer) \
95 git__free(entry->committer); \
96 git__free(entry); \
97 return git__throw(GIT_ERROR, "Failed to seek forward. Buffer size exceeded"); \
98 } \
99 buf += _increase; \
100 buf_size -= _increase; \
101 }
102
103 while (buf_size > GIT_REFLOG_SIZE_MIN) {
104 entry = git__malloc(sizeof(git_reflog_entry));
105 if (entry == NULL)
106 return GIT_ENOMEM;
107 entry->committer = NULL;
108
109 if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < GIT_SUCCESS) {
110 git__free(entry);
111 return GIT_ERROR;
112 }
113 seek_forward(GIT_OID_HEXSZ + 1);
114
115 if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < GIT_SUCCESS) {
116 git__free(entry);
117 return GIT_ERROR;
118 }
119 seek_forward(GIT_OID_HEXSZ + 1);
120
121 ptr = buf;
122
123 /* Seek forward to the end of the signature. */
124 while (*buf && *buf != '\t' && *buf != '\n')
125 seek_forward(1);
126
127 entry->committer = git__malloc(sizeof(git_signature));
128 if (entry->committer == NULL) {
129 git__free(entry);
130 return GIT_ENOMEM;
131 }
132
133 if ((error = git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf)) < GIT_SUCCESS) {
134 git__free(entry->committer);
135 git__free(entry);
136 return git__rethrow(error, "Failed to parse reflog. Could not parse signature");
137 }
138
139 if (*buf == '\t') {
140 /* We got a message. Read everything till we reach LF. */
141 seek_forward(1);
142 ptr = buf;
143
144 while (*buf && *buf != '\n')
145 seek_forward(1);
146
147 entry->msg = git__strndup(ptr, buf - ptr);
148 } else
149 entry->msg = NULL;
150
151 while (*buf && *buf == '\n' && buf_size > 1)
152 seek_forward(1);
153
154 if ((error = git_vector_insert(&log->entries, entry)) < GIT_SUCCESS)
155 return git__rethrow(error, "Failed to parse reflog. Could not add new entry");
156 }
157
158 #undef seek_forward
159
160 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse reflog");
161 }
162
163 void git_reflog_free(git_reflog *reflog)
164 {
165 unsigned int i;
166 git_reflog_entry *entry;
167
168 for (i=0; i < reflog->entries.length; i++) {
169 entry = git_vector_get(&reflog->entries, i);
170
171 git_signature_free(entry->committer);
172
173 git__free(entry->msg);
174 git__free(entry);
175 }
176
177 git_vector_free(&reflog->entries);
178 git__free(reflog->ref_name);
179 git__free(reflog);
180 }
181
182 int git_reflog_read(git_reflog **reflog, git_reference *ref)
183 {
184 int error;
185 git_buf log_path = GIT_BUF_INIT;
186 git_fbuffer log_file = GIT_FBUFFER_INIT;
187 git_reflog *log = NULL;
188
189 *reflog = NULL;
190
191 if ((error = reflog_init(&log, ref)) < GIT_SUCCESS)
192 return git__rethrow(error, "Failed to read reflog. Cannot init reflog");
193
194 error = git_buf_join_n(&log_path, '/', 3,
195 ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);
196 if (error < GIT_SUCCESS)
197 goto cleanup;
198
199 if ((error = git_futils_readbuffer(&log_file, log_path.ptr)) < GIT_SUCCESS) {
200 git__rethrow(error, "Failed to read reflog. Cannot read file `%s`", log_path.ptr);
201 goto cleanup;
202 }
203
204 if ((error = reflog_parse(log, log_file.data, log_file.len)) < GIT_SUCCESS)
205 git__rethrow(error, "Failed to read reflog");
206 else
207 *reflog = log;
208
209 cleanup:
210 if (error != GIT_SUCCESS && log != NULL)
211 git_reflog_free(log);
212 git_futils_freebuffer(&log_file);
213 git_buf_free(&log_path);
214
215 return error;
216 }
217
218 int git_reflog_write(git_reference *ref, const git_oid *oid_old,
219 const git_signature *committer, const char *msg)
220 {
221 int error;
222 char old[GIT_OID_HEXSZ+1];
223 char new[GIT_OID_HEXSZ+1];
224 git_buf log_path = GIT_BUF_INIT;
225 git_reference *r;
226 const git_oid *oid;
227
228 if ((error = git_reference_resolve(&r, ref)) < GIT_SUCCESS)
229 return git__rethrow(error,
230 "Failed to write reflog. Cannot resolve reference `%s`", ref->name);
231
232 oid = git_reference_oid(r);
233 if (oid == NULL) {
234 error = git__throw(GIT_ERROR,
235 "Failed to write reflog. Cannot resolve reference `%s`", r->name);
236 git_reference_free(r);
237 return error;
238 }
239
240 git_oid_to_string(new, GIT_OID_HEXSZ+1, oid);
241
242 git_reference_free(r);
243
244 error = git_buf_join_n(&log_path, '/', 3,
245 ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);
246 if (error < GIT_SUCCESS)
247 goto cleanup;
248
249 if (git_path_exists(log_path.ptr)) {
250 error = git_futils_mkpath2file(log_path.ptr, GIT_REFLOG_DIR_MODE);
251 if (error < GIT_SUCCESS)
252 git__rethrow(error,
253 "Failed to write reflog. Cannot create reflog directory");
254 } else if (git_path_isfile(log_path.ptr)) {
255 error = git__throw(GIT_ERROR,
256 "Failed to write reflog. `%s` is directory", log_path.ptr);
257 } else if (oid_old == NULL) {
258 error = git__throw(GIT_ERROR,
259 "Failed to write reflog. Old OID cannot be NULL for existing reference");
260 }
261
262 if (error < GIT_SUCCESS)
263 goto cleanup;
264
265 if (oid_old)
266 git_oid_to_string(old, sizeof(old), oid_old);
267 else
268 p_snprintf(old, sizeof(old), "%0*d", GIT_OID_HEXSZ, 0);
269
270 error = reflog_write(log_path.ptr, old, new, committer, msg);
271
272 cleanup:
273 git_buf_free(&log_path);
274 return error;
275 }
276
277 int git_reflog_rename(git_reference *ref, const char *new_name)
278 {
279 int error;
280 git_buf old_path = GIT_BUF_INIT;
281 git_buf new_path = GIT_BUF_INIT;
282
283 if (git_buf_join_n(&old_path, '/', 3, ref->owner->path_repository,
284 GIT_REFLOG_DIR, ref->name) &&
285 git_buf_join_n(&new_path, '/', 3, ref->owner->path_repository,
286 GIT_REFLOG_DIR, new_name))
287 error = p_rename(git_buf_cstr(&old_path), git_buf_cstr(&new_path));
288 else
289 error = GIT_ENOMEM;
290
291 git_buf_free(&old_path);
292 git_buf_free(&new_path);
293
294 return error;
295 }
296
297 int git_reflog_delete(git_reference *ref)
298 {
299 int error = GIT_SUCCESS;
300 git_buf path = GIT_BUF_INIT;
301
302 error = git_buf_join_n(&path, '/', 3,
303 ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);
304
305 if (error == GIT_SUCCESS && git_path_exists(path.ptr) == 0)
306 error = p_unlink(path.ptr);
307
308 git_buf_free(&path);
309
310 return error;
311 }
312
313 unsigned int git_reflog_entrycount(git_reflog *reflog)
314 {
315 assert(reflog);
316 return reflog->entries.length;
317 }
318
319 const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx)
320 {
321 assert(reflog);
322 return git_vector_get(&reflog->entries, idx);
323 }
324
325 const git_oid * git_reflog_entry_oidold(const git_reflog_entry *entry)
326 {
327 assert(entry);
328 return &entry->oid_old;
329 }
330
331 const git_oid * git_reflog_entry_oidnew(const git_reflog_entry *entry)
332 {
333 assert(entry);
334 return &entry->oid_cur;
335 }
336
337 git_signature * git_reflog_entry_committer(const git_reflog_entry *entry)
338 {
339 assert(entry);
340 return entry->committer;
341 }
342
343 char * git_reflog_entry_msg(const git_reflog_entry *entry)
344 {
345 assert(entry);
346 return entry->msg;
347 }