]> git.proxmox.com Git - libgit2.git/blame - src/reflog.c
reflog: add API to read or write a reference log
[libgit2.git] / src / reflog.c
CommitLineData
27df4275
MS
1/*
2 * This file is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2,
4 * as published by the Free Software Foundation.
5 *
6 * In addition to the permissions in the GNU General Public License,
7 * the authors give you unlimited permission to link the compiled
8 * version of this file into combinations with other programs,
9 * and to distribute those combinations without any restriction
10 * coming from the use of this file. (The General Public License
11 * restrictions do apply in other respects; for example, they cover
12 * modification of the file, and distribution when not linked into
13 * a combined executable.)
14 *
15 * This file is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; see the file COPYING. If not, write to
22 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 */
25
26#include "reflog.h"
27#include "repository.h"
28#include "filebuf.h"
29#include "signature.h"
30
31static int reflog_init(git_reflog **reflog, git_reference *ref)
32{
33 git_reflog *log;
34
35 *reflog = NULL;
36
37 log = git__malloc(sizeof(git_reflog));
38 if (log == NULL)
39 return GIT_ENOMEM;
40
41 memset(log, 0x0, sizeof(git_reflog));
42
43 log->ref_name = git__strdup(ref->name);
44
45 if (git_vector_init(&log->entries, 0, NULL) < 0) {
46 free(log->ref_name);
47 free(log);
48 return GIT_ENOMEM;
49 }
50
51 *reflog = log;
52
53 return GIT_SUCCESS;
54}
55
56static int reflog_write(git_repository *repo, const char *ref_name,
57 const char *oid_old, const char *oid_new,
58 const git_signature *committer, const char *msg)
59{
60 int error;
61 char log_path[GIT_PATH_MAX];
62 char *sig = NULL;
63 git_filebuf log;
64
65 assert(repo && ref_name && oid_old && oid_new && committer);
66
67 git_path_join_n(log_path, 3, repo->path_repository, GIT_REFLOG_DIR, ref_name);
68
69 if (git_futils_exists(log_path)) {
70 if ((error = git_futils_mkpath2file(log_path)) < GIT_SUCCESS)
71 return git__rethrow(error, "Failed to write reflog. Cannot create reflog directory");
72
73 } else if (git_futils_isfile(log_path))
74 return git__throw(GIT_ERROR, "Failed to write reflog. `%s` is directory", log_path);
75
76 if ((error = git_filebuf_open(&log, log_path, GIT_FILEBUF_APPEND)) < GIT_SUCCESS)
77 return git__throw(GIT_ERROR, "Failed to write reflog. Cannot open reflog `%s`", log_path);
78
79 if ((error = git_signature__write(&sig, NULL, committer)) < GIT_SUCCESS)
80 goto cleanup;
81
82 sig[strlen(sig)-1] = '\0'; /* drop LF */
83
84 if ((error = git_filebuf_printf(&log, "%s %s %s", oid_old, oid_new, sig)) < GIT_SUCCESS)
85 goto cleanup;
86
87 if (msg) {
88 if (strchr(msg, '\n')) {
89 error = git__throw(GIT_ERROR, "msg must not contain newline");
90 goto cleanup;
91 }
92
93 if ((error = git_filebuf_printf(&log, "\t%s", msg)) < GIT_SUCCESS)
94 goto cleanup;
95 }
96
97 error = git_filebuf_printf(&log, "\n");
98
99cleanup:
100 if (error < GIT_SUCCESS)
101 git_filebuf_cleanup(&log);
102 else
103 error = git_filebuf_commit(&log);
104
105 if (sig)
106 free(sig);
107
108 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write reflog");
109}
110
111static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
112{
113 int error;
114 const char *ptr;
115 git_reflog_entry *entry;
116
117#define seek_forward(_increase) { \
118 if (_increase >= buf_size) \
119 return git__throw(GIT_ERROR, "Failed to seek forward. Buffer size exceeded"); \
120 buf += _increase; \
121 buf_size -= _increase; \
122}
123
124 while (buf_size > GIT_REFLOG_SIZE_MIN) {
125 entry = git__malloc(sizeof(git_reflog_entry));
126 if (entry == NULL)
127 return GIT_ENOMEM;
128
129 entry->oid_old = git__strndup(buf, GIT_OID_HEXSZ);
130 seek_forward(GIT_OID_HEXSZ+1);
131
132 entry->oid_cur = git__strndup(buf, GIT_OID_HEXSZ);
133 seek_forward(GIT_OID_HEXSZ+1);
134
135 ptr = buf;
136
137 /* Seek forward to the end of the signature. */
138 while (*buf && *buf != '\t' && *buf != '\n')
139 seek_forward(1);
140
141 entry->committer = git__malloc(sizeof(git_signature));
142 if (entry->committer == NULL)
143 return GIT_ENOMEM;
144
145 if ((error = git_signature__parse(entry->committer, &ptr, buf + buf_size, NULL)) < GIT_SUCCESS)
146 goto cleanup;
147
148 if (*buf == '\t') {
149 /* We got a message. Read everything till we reach LF. */
150 seek_forward(1);
151 entry->msg = (char *)buf;
152
153 while (*buf && *buf != '\n')
154 seek_forward(1);
155
156 entry->msg = git__strndup(entry->msg, buf - entry->msg);
157 } else
158 entry->msg = NULL;
159
160 while (*buf && *buf == '\n' && buf_size > 1)
161 seek_forward(1);
162
163 if ((error = git_vector_insert(&log->entries, entry)) < GIT_SUCCESS)
164 goto cleanup;
165 }
166
167#undef seek_forward
168
169cleanup:
170 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse reflog");
171}
172
173void git_reflog_free(git_reflog *reflog)
174{
175 unsigned int i;
176 git_reflog_entry *entry;
177
178 for (i=0; i < reflog->entries.length; i++) {
179 entry = git_vector_get(&reflog->entries, i);
180
181 free(entry->oid_old);
182 free(entry->oid_cur);
183
184 git_signature_free(entry->committer);
185
186 free(entry->msg);
187 free(entry);
188 }
189
190 git_vector_free(&reflog->entries);
191 free(reflog->ref_name);
192 free(reflog);
193}
194
195int git_reflog_read(git_reflog **reflog, git_reference *ref)
196{
197 int error;
198 char log_path[GIT_PATH_MAX];
199 git_fbuffer log_file = GIT_FBUFFER_INIT;
200 git_reflog *log = NULL;
201
202 *reflog = NULL;
203
204 if ((error = reflog_init(&log, ref)) < GIT_SUCCESS)
205 return git__rethrow(error, "Failed to read reflog. Cannot init reflog");
206
207 git_path_join_n(log_path, 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);
208
209 if ((error = git_futils_readbuffer(&log_file, log_path)) < GIT_SUCCESS)
210 return git__rethrow(error, "Failed to read reflog. Cannot read file `%s`", log_path);
211
212 error = reflog_parse(log, log_file.data, log_file.len);
213
214 git_futils_freebuffer(&log_file);
215
216 if (error == GIT_SUCCESS)
217 *reflog = log;
218
219 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to read reflog");
220}
221
222int git_reflog_write(git_reference *ref, const git_oid *oid_old,
223 const git_signature *committer, const char *msg)
224{
225 int error;
226 char old[GIT_OID_HEXSZ+1];
227 char new[GIT_OID_HEXSZ+1];
228 git_reference *r;
229 const git_oid *oid;
230
231 if ((error = git_reference_resolve(&r, ref)) < GIT_SUCCESS)
232 return git__rethrow(error, "Failed to write reflog. Cannot resolve reference `%s`", ref->name);
233
234 oid = git_reference_oid(r);
235 if (oid == NULL)
236 return git__throw(GIT_ERROR, "Failed to write reflog. Cannot resolve reference `%s`", r->name);
237
238 git_oid_to_string(new, GIT_OID_HEXSZ+1, oid);
239
240 if (oid_old)
241 git_oid_to_string(old, GIT_OID_HEXSZ+1, oid_old);
242 else
243 snprintf(old, GIT_OID_HEXSZ+1, "%0*d", GIT_OID_HEXSZ, 0);
244
245 return reflog_write(ref->owner, ref->name, old, new, committer, msg);
246}
247
248unsigned int git_reflog_entrycount(git_reflog *reflog)
249{
250 assert(reflog);
251 return reflog->entries.length;
252}
253
254const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx)
255{
256 assert(reflog);
257 return git_vector_get(&reflog->entries, idx);
258}
259
260char * git_reflog_entry_oidold(const git_reflog_entry *entry)
261{
262 assert(entry);
263 return entry->oid_old;
264}
265
266char * git_reflog_entry_oidnew(const git_reflog_entry *entry)
267{
268 assert(entry);
269 return entry->oid_cur;
270}
271
272git_signature * git_reflog_entry_committer(const git_reflog_entry *entry)
273{
274 assert(entry);
275 return entry->committer;
276}
277
278char * git_reflog_entry_msg(const git_reflog_entry *entry)
279{
280 assert(entry);
281 return entry->msg;
282}