Commit 27df4275 by schu Committed by Vicent Marti

reflog: add API to read or write a reference log

So far libgit2 didn't support reference logs (reflog). Add a new
git_reflog_* API for basic reading and writing of reflogs:

* git_reflog_read
* git_reflog_write
* git_reflog_free

Signed-off-by: schu <schu-github@schulog.org>
parent 6727e300
......@@ -44,6 +44,7 @@
#include "git2/repository.h"
#include "git2/revwalk.h"
#include "git2/refs.h"
#include "git2/reflog.h"
#include "git2/object.h"
#include "git2/blob.h"
......
/*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2,
* as published by the Free Software Foundation.
*
* In addition to the permissions in the GNU General Public License,
* the authors give you unlimited permission to link the compiled
* version of this file into combinations with other programs,
* and to distribute those combinations without any restriction
* coming from the use of this file. (The General Public License
* restrictions do apply in other respects; for example, they cover
* modification of the file, and distribution when not linked into
* a combined executable.)
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef INCLUDE_git_reflog_h__
#define INCLUDE_git_reflog_h__
#include "common.h"
#include "types.h"
#include "oid.h"
/**
* @file git2/reflog.h
* @brief Git reflog management routines
* @defgroup git_reflog Git reflog management routines
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
/**
* Read the reflog for the given reference
*
* The reflog must be freed manually by using
* git_reflog_free().
*
* @param reflog pointer to reflog
* @param ref reference to read the reflog for
* @return GIT_SUCCESS on success; error code otherwise
*/
GIT_EXTERN(int) git_reflog_read(git_reflog **reflog, git_reference *ref);
/**
* Write a new reflog for the given reference
*
* If there is no reflog file for the given
* reference yet, it will be created.
*
* `oid_old` may be NULL in case it's a new reference.
*
* `msg` is optional and can be NULL.
*
* @param ref the changed reference
* @param oid_old the OID the reference was pointing to
* @param committer the signature of the committer
* @param msg the reflog message
* @return GIT_SUCCESS on success; error code otherwise
*/
GIT_EXTERN(int) git_reflog_write(git_reference *ref, const git_oid *oid_old, const git_signature *committer, const char *msg);
/**
* Get the number of log entries in a reflog
*
* @param reflog the previously loaded reflog
* @return the number of log entries
*/
GIT_EXTERN(unsigned int) git_reflog_entrycount(git_reflog *reflog);
/**
* Lookup an entry by its index
*
* @param reflog a previously loaded reflog
* @param idx the position to lookup
* @param the entry; NULL if not found
*/
GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx);
/**
* Get the old oid
*
* @param entry a reflog entry
* @return the old oid
*/
GIT_EXTERN(char *) git_reflog_entry_oidold(const git_reflog_entry *entry);
/**
* Get the new oid
*
* @param entry a reflog entry
* @return the new oid at this time
*/
GIT_EXTERN(char *) git_reflog_entry_oidnew(const git_reflog_entry *entry);
/**
* Get the committer of this entry
*
* @param entry a reflog entry
* @return the committer
*/
GIT_EXTERN(git_signature *) git_reflog_entry_committer(const git_reflog_entry *entry);
/**
* Get the log msg
*
* @param entry a reflog entry
* @return the log msg
*/
GIT_EXTERN(char *) git_reflog_entry_msg(const git_reflog_entry *entry);
/**
* Free the reflog
*
* @param reflog reflog to free
*/
GIT_EXTERN(void) git_reflog_free(git_reflog *reflog);
/** @} */
GIT_END_DECL
#endif
......@@ -141,6 +141,12 @@ typedef struct git_config git_config;
/** Interface to access a configuration file */
typedef struct git_config_file git_config_file;
/** Representation of a reference log entry */
typedef struct git_reflog_entry git_reflog_entry;
/** Representation of a reference log */
typedef struct git_reflog git_reflog;
/** Time in a signature */
typedef struct git_time {
git_time_t time; /** time in seconds from epoch */
......
/*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2,
* as published by the Free Software Foundation.
*
* In addition to the permissions in the GNU General Public License,
* the authors give you unlimited permission to link the compiled
* version of this file into combinations with other programs,
* and to distribute those combinations without any restriction
* coming from the use of this file. (The General Public License
* restrictions do apply in other respects; for example, they cover
* modification of the file, and distribution when not linked into
* a combined executable.)
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "reflog.h"
#include "repository.h"
#include "filebuf.h"
#include "signature.h"
static int reflog_init(git_reflog **reflog, git_reference *ref)
{
git_reflog *log;
*reflog = NULL;
log = git__malloc(sizeof(git_reflog));
if (log == NULL)
return GIT_ENOMEM;
memset(log, 0x0, sizeof(git_reflog));
log->ref_name = git__strdup(ref->name);
if (git_vector_init(&log->entries, 0, NULL) < 0) {
free(log->ref_name);
free(log);
return GIT_ENOMEM;
}
*reflog = log;
return GIT_SUCCESS;
}
static int reflog_write(git_repository *repo, const char *ref_name,
const char *oid_old, const char *oid_new,
const git_signature *committer, const char *msg)
{
int error;
char log_path[GIT_PATH_MAX];
char *sig = NULL;
git_filebuf log;
assert(repo && ref_name && oid_old && oid_new && committer);
git_path_join_n(log_path, 3, repo->path_repository, GIT_REFLOG_DIR, ref_name);
if (git_futils_exists(log_path)) {
if ((error = git_futils_mkpath2file(log_path)) < GIT_SUCCESS)
return git__rethrow(error, "Failed to write reflog. Cannot create reflog directory");
} else if (git_futils_isfile(log_path))
return git__throw(GIT_ERROR, "Failed to write reflog. `%s` is directory", log_path);
if ((error = git_filebuf_open(&log, log_path, GIT_FILEBUF_APPEND)) < GIT_SUCCESS)
return git__throw(GIT_ERROR, "Failed to write reflog. Cannot open reflog `%s`", log_path);
if ((error = git_signature__write(&sig, NULL, committer)) < GIT_SUCCESS)
goto cleanup;
sig[strlen(sig)-1] = '\0'; /* drop LF */
if ((error = git_filebuf_printf(&log, "%s %s %s", oid_old, oid_new, sig)) < GIT_SUCCESS)
goto cleanup;
if (msg) {
if (strchr(msg, '\n')) {
error = git__throw(GIT_ERROR, "msg must not contain newline");
goto cleanup;
}
if ((error = git_filebuf_printf(&log, "\t%s", msg)) < GIT_SUCCESS)
goto cleanup;
}
error = git_filebuf_printf(&log, "\n");
cleanup:
if (error < GIT_SUCCESS)
git_filebuf_cleanup(&log);
else
error = git_filebuf_commit(&log);
if (sig)
free(sig);
return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write reflog");
}
static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
{
int error;
const char *ptr;
git_reflog_entry *entry;
#define seek_forward(_increase) { \
if (_increase >= buf_size) \
return git__throw(GIT_ERROR, "Failed to seek forward. Buffer size exceeded"); \
buf += _increase; \
buf_size -= _increase; \
}
while (buf_size > GIT_REFLOG_SIZE_MIN) {
entry = git__malloc(sizeof(git_reflog_entry));
if (entry == NULL)
return GIT_ENOMEM;
entry->oid_old = git__strndup(buf, GIT_OID_HEXSZ);
seek_forward(GIT_OID_HEXSZ+1);
entry->oid_cur = git__strndup(buf, GIT_OID_HEXSZ);
seek_forward(GIT_OID_HEXSZ+1);
ptr = buf;
/* Seek forward to the end of the signature. */
while (*buf && *buf != '\t' && *buf != '\n')
seek_forward(1);
entry->committer = git__malloc(sizeof(git_signature));
if (entry->committer == NULL)
return GIT_ENOMEM;
if ((error = git_signature__parse(entry->committer, &ptr, buf + buf_size, NULL)) < GIT_SUCCESS)
goto cleanup;
if (*buf == '\t') {
/* We got a message. Read everything till we reach LF. */
seek_forward(1);
entry->msg = (char *)buf;
while (*buf && *buf != '\n')
seek_forward(1);
entry->msg = git__strndup(entry->msg, buf - entry->msg);
} else
entry->msg = NULL;
while (*buf && *buf == '\n' && buf_size > 1)
seek_forward(1);
if ((error = git_vector_insert(&log->entries, entry)) < GIT_SUCCESS)
goto cleanup;
}
#undef seek_forward
cleanup:
return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse reflog");
}
void git_reflog_free(git_reflog *reflog)
{
unsigned int i;
git_reflog_entry *entry;
for (i=0; i < reflog->entries.length; i++) {
entry = git_vector_get(&reflog->entries, i);
free(entry->oid_old);
free(entry->oid_cur);
git_signature_free(entry->committer);
free(entry->msg);
free(entry);
}
git_vector_free(&reflog->entries);
free(reflog->ref_name);
free(reflog);
}
int git_reflog_read(git_reflog **reflog, git_reference *ref)
{
int error;
char log_path[GIT_PATH_MAX];
git_fbuffer log_file = GIT_FBUFFER_INIT;
git_reflog *log = NULL;
*reflog = NULL;
if ((error = reflog_init(&log, ref)) < GIT_SUCCESS)
return git__rethrow(error, "Failed to read reflog. Cannot init reflog");
git_path_join_n(log_path, 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);
if ((error = git_futils_readbuffer(&log_file, log_path)) < GIT_SUCCESS)
return git__rethrow(error, "Failed to read reflog. Cannot read file `%s`", log_path);
error = reflog_parse(log, log_file.data, log_file.len);
git_futils_freebuffer(&log_file);
if (error == GIT_SUCCESS)
*reflog = log;
return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to read reflog");
}
int git_reflog_write(git_reference *ref, const git_oid *oid_old,
const git_signature *committer, const char *msg)
{
int error;
char old[GIT_OID_HEXSZ+1];
char new[GIT_OID_HEXSZ+1];
git_reference *r;
const git_oid *oid;
if ((error = git_reference_resolve(&r, ref)) < GIT_SUCCESS)
return git__rethrow(error, "Failed to write reflog. Cannot resolve reference `%s`", ref->name);
oid = git_reference_oid(r);
if (oid == NULL)
return git__throw(GIT_ERROR, "Failed to write reflog. Cannot resolve reference `%s`", r->name);
git_oid_to_string(new, GIT_OID_HEXSZ+1, oid);
if (oid_old)
git_oid_to_string(old, GIT_OID_HEXSZ+1, oid_old);
else
snprintf(old, GIT_OID_HEXSZ+1, "%0*d", GIT_OID_HEXSZ, 0);
return reflog_write(ref->owner, ref->name, old, new, committer, msg);
}
unsigned int git_reflog_entrycount(git_reflog *reflog)
{
assert(reflog);
return reflog->entries.length;
}
const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx)
{
assert(reflog);
return git_vector_get(&reflog->entries, idx);
}
char * git_reflog_entry_oidold(const git_reflog_entry *entry)
{
assert(entry);
return entry->oid_old;
}
char * git_reflog_entry_oidnew(const git_reflog_entry *entry)
{
assert(entry);
return entry->oid_cur;
}
git_signature * git_reflog_entry_committer(const git_reflog_entry *entry)
{
assert(entry);
return entry->committer;
}
char * git_reflog_entry_msg(const git_reflog_entry *entry)
{
assert(entry);
return entry->msg;
}
#ifndef INCLUDE_reflog_h__
#define INCLUDE_reflog_h__
#include "common.h"
#include "git2/reflog.h"
#include "vector.h"
#define GIT_REFLOG_DIR "logs/"
#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17)
struct git_reflog_entry {
char *oid_old;
char *oid_cur;
git_signature *committer;
char *msg;
};
struct git_reflog {
char *ref_name;
git_vector entries;
};
#endif /* INCLUDE_reflog_h__ */
......@@ -27,6 +27,9 @@
#include "repository.h"
#include "git2/reflog.h"
#include "reflog.h"
static const char *loose_tag_ref_name = "refs/tags/e90810b";
static const char *non_existing_tag_ref_name = "refs/tags/i-do-not-exist";
......@@ -993,6 +996,61 @@ BEGIN_TEST(list1, "try to list only the symbolic references")
git_repository_free(repo);
END_TEST
static const char *new_ref = "refs/heads/test-reflog";
BEGIN_TEST(reflog0, "write a reflog for a given reference")
git_repository *repo;
git_reference *ref;
git_oid oid;
git_signature *committer;
git_oid_fromstr(&oid, current_master_tip);
must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
must_pass(git_reference_create_oid(&ref, repo, new_ref, &oid, 0));
must_pass(git_reference_lookup(&ref, repo, new_ref));
committer = git_signature_now("foo", "foo@bar");
must_pass(git_reflog_write(ref, NULL, committer, NULL));
must_fail(git_reflog_write(ref, NULL, committer, "no\nnewline"));
must_pass(git_reflog_write(ref, &oid, committer, "commit: bla bla"));
git_repository_free(repo);
END_TEST
BEGIN_TEST(reflog1, "read a reflog for a given reference")
unsigned int i;
git_repository *repo;
git_reference *ref;
git_reflog *reflog;
git_reflog_entry *GIT_UNUSED(entry);
must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
must_pass(git_reference_lookup(&ref, repo, new_ref));
must_pass(git_reflog_read(&reflog, ref));
for (i=0; i<reflog->entries.length; ++i) {
entry = git_vector_get(&reflog->entries, i);
/*
fprintf(stderr, "\nold: %s\n", entry->oid_old);
fprintf(stderr, "cur: %s\n", entry->oid_cur);
fprintf(stderr, "name: %s\n", entry->committer->name);
fprintf(stderr, "mail: %s\n", entry->committer->email);
if (entry->msg)
fprintf(stderr, "msg: %s\n", entry->msg);
*/
}
git_reflog_free(reflog);
must_pass(git_reference_delete(ref));
git_repository_free(repo);
END_TEST
BEGIN_SUITE(refs)
ADD_TEST(readtag0);
......@@ -1034,6 +1092,10 @@ BEGIN_SUITE(refs)
ADD_TEST(rename8);
ADD_TEST(delete0);
ADD_TEST(list0);
ADD_TEST(list1);
ADD_TEST(reflog0);
ADD_TEST(reflog1);
END_SUITE
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment