]> git.proxmox.com Git - libgit2.git/commitdiff
reflog: add API to read or write a reference log
authorschu <schu-github@schulog.org>
Tue, 28 Jun 2011 12:13:12 +0000 (14:13 +0200)
committerVicent Marti <tanoku@gmail.com>
Sat, 9 Jul 2011 00:40:16 +0000 (02:40 +0200)
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>
include/git2.h
include/git2/reflog.h [new file with mode: 0644]
include/git2/types.h
src/reflog.c [new file with mode: 0644]
src/reflog.h [new file with mode: 0644]
tests/t10-refs.c

index b5c693a82cf69a3f4134f244ee52b70486c41f00..a8a43065479309769249f00fcd5f84a4900612e3 100644 (file)
@@ -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"
diff --git a/include/git2/reflog.h b/include/git2/reflog.h
new file mode 100644 (file)
index 0000000..8a2edca
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * 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
index bc1b8a6c7924cff2828bb8618d818c645d03f057..41d5482341d3b449ee1fa8fe4aca548e52bede75 100644 (file)
@@ -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 */
diff --git a/src/reflog.c b/src/reflog.c
new file mode 100644 (file)
index 0000000..63c4c50
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * 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;
+}
diff --git a/src/reflog.h b/src/reflog.h
new file mode 100644 (file)
index 0000000..da352ca
--- /dev/null
@@ -0,0 +1,26 @@
+#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__ */
index 3310bc72c75019c78e8f7d20b580a28cd21de874..e004625bdaeabe65fdce0bf431e2cdf13035865c 100644 (file)
@@ -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