[hacktree: 4/4] Add probably working commits
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [hacktree: 4/4] Add probably working commits
- Date: Sat, 15 Oct 2011 01:11:03 +0000 (UTC)
commit 98a043c67113fdda0190f5170e881dd4255f9c05
Author: Colin Walters <walters verbum org>
Date: Thu Oct 13 17:11:01 2011 -0400
Add probably working commits
Makefile-hacktree.am | 1 +
TODO | 4 +-
configure.ac | 13 +
src/ht-builtin-commit.c | 115 +++++
src/ht-builtin-fsck.c | 1 -
src/ht-builtins.h | 1 +
src/libhacktree/hacktree-core.c | 86 +++-
src/libhacktree/hacktree-core.h | 50 ++-
src/libhacktree/hacktree-repo.c | 926 ++++++++++++++++++++++++++++++++++++++-
src/libhacktree/hacktree-repo.h | 10 +
src/libhtutil/ht-gio-utils.c | 51 +++-
src/libhtutil/ht-gio-utils.h | 8 +-
src/libhtutil/ht-unix-utils.c | 119 +++++
src/libhtutil/ht-unix-utils.h | 12 +
src/main.c | 1 +
tests/t0002-commit-one.sh | 38 ++
16 files changed, 1394 insertions(+), 42 deletions(-)
---
diff --git a/Makefile-hacktree.am b/Makefile-hacktree.am
index 6b25cdc..b62081b 100644
--- a/Makefile-hacktree.am
+++ b/Makefile-hacktree.am
@@ -46,6 +46,7 @@ bin_PROGRAMS += hacktree
hacktree_SOURCES = src/main.c \
src/ht-builtins.h \
+ src/ht-builtin-commit.c \
src/ht-builtin-init.c \
src/ht-builtin-link-file.c \
src/ht-builtin-fsck.c \
diff --git a/TODO b/TODO
index aeff7f7..dd4bc1d 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,5 @@
-* hacktree import-tar
- - Accepts tarball on stdin, stores the objects
* tree and commit objects
* hacktree import
+* hacktree import-tar
+ - Accepts tarball on stdin, stores the objects
* branches
diff --git a/configure.ac b/configure.ac
index 49b3fa6..9bb5792 100644
--- a/configure.ac
+++ b/configure.ac
@@ -11,6 +11,19 @@ AM_SILENT_RULES([yes])
AC_PROG_CC
AM_PROG_CC_C_O
+changequote(,)dnl
+if test "x$GCC" = "xyes"; then
+ case " $CFLAGS " in
+ *[\ \ ]-Wall[\ \ ]*) ;;
+ *) CFLAGS="$CFLAGS -Wall" ;;
+ esac
+ case " $CFLAGS " in
+ *[\ \ ]-Wmissing-prototypes[\ \ ]*) ;;
+ *) CFLAGS="$CFLAGS -Wmissing-prototypes" ;;
+ esac
+fi
+changequote([,])dnl
+
# Initialize libtool
LT_PREREQ([2.2])
LT_INIT
diff --git a/src/ht-builtin-commit.c b/src/ht-builtin-commit.c
new file mode 100644
index 0000000..1273f65
--- /dev/null
+++ b/src/ht-builtin-commit.c
@@ -0,0 +1,115 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters verbum org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#include "config.h"
+
+#include "ht-builtins.h"
+#include "hacktree.h"
+
+#include <glib/gi18n.h>
+
+static char *repo_path;
+static char *subject;
+static char *body;
+static char **additions;
+static char **removals;
+
+static GOptionEntry options[] = {
+ { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+ { "subject", 's', 0, G_OPTION_ARG_STRING, &subject, "One line subject", "subject" },
+ { "body", 'b', 0, G_OPTION_ARG_STRING, &body, "Full description", "body" },
+ { "add", 'a', 0, G_OPTION_ARG_FILENAME_ARRAY, &additions, "Relative file path to add", "filename" },
+ { "remove", 'r', 0, G_OPTION_ARG_FILENAME_ARRAY, &removals, "Relative file path to remove", "filename" },
+ { NULL }
+};
+
+gboolean
+hacktree_builtin_commit (int argc, char **argv, const char *prefix, GError **error)
+{
+ GOptionContext *context;
+ gboolean ret = FALSE;
+ HacktreeRepo *repo = NULL;
+ GPtrArray *additions_array = NULL;
+ GPtrArray *removals_array = NULL;
+ GChecksum *commit_checksum = NULL;
+ char **iter;
+
+ context = g_option_context_new ("- Commit a new revision");
+ g_option_context_add_main_entries (context, options, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, error))
+ goto out;
+
+ if (repo_path == NULL)
+ repo_path = ".";
+ if (prefix == NULL)
+ prefix = ".";
+
+ repo = hacktree_repo_new (repo_path);
+ if (!hacktree_repo_check (repo, error))
+ goto out;
+
+ if (!(removals || additions))
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No additions or removals specified");
+ goto out;
+ }
+
+ if (!subject)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "A subject must be specified with --subject");
+ goto out;
+ }
+
+ additions_array = g_ptr_array_new ();
+ removals_array = g_ptr_array_new ();
+
+ if (additions)
+ for (iter = additions; *iter; iter++)
+ g_ptr_array_add (additions_array, *iter);
+ if (removals)
+ for (iter = removals; *iter; iter++)
+ g_ptr_array_add (removals_array, *iter);
+
+ if (!hacktree_repo_commit (repo, subject, body, NULL,
+ prefix, additions_array,
+ removals_array,
+ &commit_checksum,
+ error))
+ goto out;
+
+
+ ret = TRUE;
+ g_print ("%s\n", g_checksum_get_string (commit_checksum));
+ out:
+ if (context)
+ g_option_context_free (context);
+ g_clear_object (&repo);
+ if (removals_array)
+ g_ptr_array_free (removals_array, TRUE);
+ if (additions_array)
+ g_ptr_array_free (additions_array, TRUE);
+ if (commit_checksum)
+ g_checksum_free (commit_checksum);
+ return ret;
+}
diff --git a/src/ht-builtin-fsck.c b/src/ht-builtin-fsck.c
index 1dc5257..b47d932 100644
--- a/src/ht-builtin-fsck.c
+++ b/src/ht-builtin-fsck.c
@@ -94,7 +94,6 @@ hacktree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error
HtFsckData data;
gboolean ret = FALSE;
HacktreeRepo *repo = NULL;
- int i;
context = g_option_context_new ("- Check the repository for consistency");
g_option_context_add_main_entries (context, options, NULL);
diff --git a/src/ht-builtins.h b/src/ht-builtins.h
index f9d8798..e9c8c6e 100644
--- a/src/ht-builtins.h
+++ b/src/ht-builtins.h
@@ -36,6 +36,7 @@ typedef struct {
int flags; /* HacktreeBuiltinFlags */
} HacktreeBuiltin;
+gboolean hacktree_builtin_commit (int argc, char **argv, const char *prefix, GError **error);
gboolean hacktree_builtin_init (int argc, char **argv, const char *prefix, GError **error);
gboolean hacktree_builtin_link_file (int argc, char **argv, const char *prefix, GError **error);
gboolean hacktree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error);
diff --git a/src/libhacktree/hacktree-core.c b/src/libhacktree/hacktree-core.c
index ddda1b5..165dfe7 100644
--- a/src/libhacktree/hacktree-core.c
+++ b/src/libhacktree/hacktree-core.c
@@ -24,10 +24,16 @@
#include "hacktree.h"
#include "htutil.h"
-char *
+#include <sys/types.h>
+#include <attr/xattr.h>
+
+static char *
stat_to_string (struct stat *stbuf)
{
- return g_strdup_printf ("%d:%d:%d", stbuf->st_mode, stbuf->st_uid, stbuf->st_gid);
+ return g_strdup_printf ("%u:%u:%u",
+ (guint32)(stbuf->st_mode & ~S_IFMT),
+ (guint32)stbuf->st_uid,
+ (guint32)stbuf->st_gid);
}
static char *
@@ -56,6 +62,52 @@ canonicalize_xattrs (char *xattr_string, size_t len)
}
gboolean
+hacktree_get_xattrs_for_directory (const char *path,
+ char **out_xattrs, /* out */
+ gsize *out_len, /* out */
+ GError **error)
+{
+ gboolean ret = FALSE;
+ char *xattrs = NULL;
+ ssize_t bytes_read;
+
+ bytes_read = llistxattr (path, NULL, 0);
+
+ if (bytes_read < 0)
+ {
+ if (errno != ENOTSUP)
+ {
+ ht_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ }
+ else if (bytes_read > 0)
+ {
+ xattrs = g_malloc (bytes_read);
+ if (!llistxattr (path, xattrs, bytes_read))
+ {
+ ht_util_set_error_from_errno (error, errno);
+ g_free (xattrs);
+ xattrs = NULL;
+ goto out;
+ }
+
+ *out_xattrs = canonicalize_xattrs (xattrs, bytes_read);
+ *out_len = (gsize)bytes_read;
+ }
+ else
+ {
+ *out_xattrs = NULL;
+ *out_len = 0;
+ }
+
+ ret = TRUE;
+ out:
+ g_free (xattrs);
+ return ret;
+}
+
+gboolean
hacktree_stat_and_checksum_file (int dir_fd, const char *path,
GChecksum **out_checksum,
struct stat *out_stbuf,
@@ -73,6 +125,7 @@ hacktree_stat_and_checksum_file (int dir_fd, const char *path,
gboolean ret = FALSE;
char *symlink_target = NULL;
char *device_id = NULL;
+ struct stat stbuf;
basename = g_path_get_basename (path);
@@ -89,13 +142,13 @@ hacktree_stat_and_checksum_file (int dir_fd, const char *path,
dir_fd = dirfd (temp_dir);
}
- if (fstatat (dir_fd, basename, out_stbuf, AT_SYMLINK_NOFOLLOW) < 0)
+ if (fstatat (dir_fd, basename, &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
{
ht_util_set_error_from_errno (error, errno);
goto out;
}
- if (!S_ISLNK(out_stbuf->st_mode))
+ if (!S_ISLNK(stbuf.st_mode))
{
fd = ht_util_open_file_read_at (dir_fd, basename, error);
if (fd < 0)
@@ -105,10 +158,10 @@ hacktree_stat_and_checksum_file (int dir_fd, const char *path,
}
}
- stat_string = stat_to_string (out_stbuf);
+ stat_string = stat_to_string (&stbuf);
/* FIXME - Add llistxattrat */
- if (!S_ISLNK(out_stbuf->st_mode))
+ if (!S_ISLNK(stbuf.st_mode))
bytes_read = flistxattr (fd, NULL, 0);
else
bytes_read = llistxattr (path, NULL, 0);
@@ -121,12 +174,12 @@ hacktree_stat_and_checksum_file (int dir_fd, const char *path,
goto out;
}
}
- if (errno != ENOTSUP)
+ else if (bytes_read > 0)
{
gboolean tmp;
xattrs = g_malloc (bytes_read);
/* FIXME - Add llistxattrat */
- if (!S_ISLNK(out_stbuf->st_mode))
+ if (!S_ISLNK(stbuf.st_mode))
tmp = flistxattr (fd, xattrs, bytes_read);
else
tmp = llistxattr (path, xattrs, bytes_read);
@@ -142,7 +195,7 @@ hacktree_stat_and_checksum_file (int dir_fd, const char *path,
content_sha256 = g_checksum_new (G_CHECKSUM_SHA256);
- if (S_ISREG(out_stbuf->st_mode))
+ if (S_ISREG(stbuf.st_mode))
{
guint8 buf[8192];
@@ -154,7 +207,7 @@ hacktree_stat_and_checksum_file (int dir_fd, const char *path,
goto out;
}
}
- else if (S_ISLNK(out_stbuf->st_mode))
+ else if (S_ISLNK(stbuf.st_mode))
{
symlink_target = g_malloc (PATH_MAX);
@@ -163,12 +216,12 @@ hacktree_stat_and_checksum_file (int dir_fd, const char *path,
ht_util_set_error_from_errno (error, errno);
goto out;
}
- g_checksum_update (content_sha256, symlink_target, strlen (symlink_target));
+ g_checksum_update (content_sha256, (guint8*)symlink_target, strlen (symlink_target));
}
- else if (S_ISCHR(out_stbuf->st_mode) || S_ISBLK(out_stbuf->st_mode))
+ else if (S_ISCHR(stbuf.st_mode) || S_ISBLK(stbuf.st_mode))
{
- device_id = g_strdup_printf ("%d", out_stbuf->st_rdev);
- g_checksum_update (content_sha256, device_id, strlen (device_id));
+ device_id = g_strdup_printf ("%u", (guint)stbuf.st_rdev);
+ g_checksum_update (content_sha256, (guint8*)device_id, strlen (device_id));
}
else
{
@@ -181,9 +234,10 @@ hacktree_stat_and_checksum_file (int dir_fd, const char *path,
content_and_meta_sha256 = g_checksum_copy (content_sha256);
- g_checksum_update (content_and_meta_sha256, stat_string, strlen (stat_string));
- g_checksum_update (content_and_meta_sha256, xattrs_canonicalized, strlen (stat_string));
+ g_checksum_update (content_and_meta_sha256, (guint8*)stat_string, strlen (stat_string));
+ g_checksum_update (content_and_meta_sha256, (guint8*)xattrs_canonicalized, strlen (stat_string));
+ *out_stbuf = stbuf;
*out_checksum = content_and_meta_sha256;
ret = TRUE;
out:
diff --git a/src/libhacktree/hacktree-core.h b/src/libhacktree/hacktree-core.h
index 124a90a..b5ed901 100644
--- a/src/libhacktree/hacktree-core.h
+++ b/src/libhacktree/hacktree-core.h
@@ -18,7 +18,6 @@
*
* Author: Colin Walters <walters verbum org>
*/
-/* hacktree-repo.h */
#ifndef _HACKTREE_CORE
#define _HACKTREE_CORE
@@ -27,6 +26,55 @@
G_BEGIN_DECLS
+#define HACKTREE_EMPTY_STRING_SHA256 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+
+typedef enum {
+ HACKTREE_SERIALIZED_TREE_VARIANT = 1,
+ HACKTREE_SERIALIZED_COMMIT_VARIANT = 2,
+ HACKTREE_SERIALIZED_DIRMETA_VARIANT = 3
+} HacktreeSerializedVariantType;
+
+#define HACKTREE_SERIALIZED_VARIANT_FORMAT "(uv)"
+
+#define HACKTREE_DIR_META_VERSION 0
+/*
+ * dirmeta objects:
+ * u - Version
+ * u - uid
+ * u - gid
+ * u - mode
+ * ay - xattrs
+ */
+#define HACKTREE_DIRMETA_GVARIANT_FORMAT "(uuuuay)"
+
+#define HACKTREE_TREE_VERSION 0
+/*
+ * Tree objects:
+ * u - Version
+ * a{sv} - Metadata
+ * a(ss) - array of (filename, checksum) for files
+ * a(sss) - array of (dirname, tree_checksum, meta_checksum) for directories
+ */
+#define HACKTREE_TREE_GVARIANT_FORMAT "(ua{sv}a(ss)a(sss)"
+
+#define HACKTREE_COMMIT_VERSION 0
+/*
+ * Commit objects:
+ * u - Version
+ * a{sv} - Metadata
+ * s - parent checksum
+ * s - subject
+ * s - body
+ * t - Timestamp in seconds since the epoch (UTC)
+ * s - Tree SHA256
+ */
+#define HACKTREE_COMMIT_GVARIANT_FORMAT "(ua{sv}sssts)"
+
+gboolean hacktree_get_xattrs_for_directory (const char *path,
+ char **out_xattrs,
+ gsize *out_len,
+ GError **error);
+
gboolean hacktree_stat_and_checksum_file (int dirfd, const char *path,
GChecksum **out_checksum,
struct stat *out_stbuf,
diff --git a/src/libhacktree/hacktree-repo.c b/src/libhacktree/hacktree-repo.c
index 7f6fce0..908ed57 100644
--- a/src/libhacktree/hacktree-repo.c
+++ b/src/libhacktree/hacktree-repo.c
@@ -24,6 +24,17 @@
#include "hacktree.h"
#include "htutil.h"
+#include <gio/gunixoutputstream.h>
+
+static gboolean
+link_one_file (HacktreeRepo *self, const char *path,
+ gboolean ignore_exists, gboolean force,
+ GChecksum **out_checksum,
+ GError **error);
+static char *
+get_object_path_for_checksum (HacktreeRepo *self,
+ const char *checksum);
+
enum {
PROP_0,
@@ -39,9 +50,13 @@ typedef struct _HacktreeRepoPrivate HacktreeRepoPrivate;
struct _HacktreeRepoPrivate {
char *path;
+ GFile *repo_file;
+ char *head_ref_path;
+ char *index_path;
char *objects_path;
gboolean inited;
+ char *current_head;
};
static void
@@ -51,7 +66,11 @@ hacktree_repo_finalize (GObject *object)
HacktreeRepoPrivate *priv = GET_PRIVATE (self);
g_free (priv->path);
+ g_clear_object (&priv->repo_file);
+ g_free (priv->head_ref_path);
+ g_free (priv->index_path);
g_free (priv->objects_path);
+ g_free (priv->current_head);
G_OBJECT_CLASS (hacktree_repo_parent_class)->finalize (object);
}
@@ -111,8 +130,11 @@ hacktree_repo_constructor (GType gtype,
priv = GET_PRIVATE (object);
g_assert (priv->path != NULL);
-
+
+ priv->repo_file = g_file_new_for_path (priv->path);
+ priv->head_ref_path = g_build_filename (priv->path, HACKTREE_REPO_DIR, "HEAD", NULL);
priv->objects_path = g_build_filename (priv->path, HACKTREE_REPO_DIR, "objects", NULL);
+ priv->index_path = g_build_filename (priv->path, HACKTREE_REPO_DIR, "index", NULL);
return object;
}
@@ -141,8 +163,6 @@ hacktree_repo_class_init (HacktreeRepoClass *klass)
static void
hacktree_repo_init (HacktreeRepo *self)
{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
-
}
HacktreeRepo*
@@ -151,11 +171,44 @@ hacktree_repo_new (const char *path)
return g_object_new (HACKTREE_TYPE_REPO, "path", path, NULL);
}
+static gboolean
+parse_checksum_file (HacktreeRepo *self,
+ const char *path,
+ char **sha256,
+ GError **error)
+{
+ GError *temp_error = NULL;
+ gboolean ret = FALSE;
+ char *ret_sha256 = NULL;
+
+ ret_sha256 = ht_util_get_file_contents_utf8 (path, &temp_error);
+ if (ret_sha256 == NULL)
+ {
+ if (g_error_matches (temp_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ {
+ g_clear_error (&temp_error);
+ }
+ else
+ {
+ g_propagate_error (error, temp_error);
+ goto out;
+ }
+ }
+
+ *sha256 = ret_sha256;
+ ret = TRUE;
+ out:
+ return ret;
+}
+
gboolean
hacktree_repo_check (HacktreeRepo *self, GError **error)
{
HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+ if (priv->inited)
+ return TRUE;
+
if (!g_file_test (priv->objects_path, G_FILE_TEST_IS_DIR))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
@@ -164,40 +217,228 @@ hacktree_repo_check (HacktreeRepo *self, GError **error)
}
priv->inited = TRUE;
- return TRUE;
+
+ return parse_checksum_file (self, priv->head_ref_path, &priv->current_head, error);
}
+static gboolean
+import_gvariant_object (HacktreeRepo *self,
+ HacktreeSerializedVariantType type,
+ GVariant *variant,
+ GChecksum **out_checksum,
+ GError **error)
+{
+ HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+ GVariant *serialized = NULL;
+ gboolean ret = FALSE;
+ gsize bytes_written;
+ char *tmp_name = NULL;
+ int fd = -1;
+ GUnixOutputStream *stream = NULL;
+
+ serialized = g_variant_new ("(uv)", (guint32)type, variant);
+
+ tmp_name = g_build_filename (priv->objects_path, "variant-tmp-XXXXXX", NULL);
+ fd = mkstemp (tmp_name);
+ if (fd < 0)
+ {
+ ht_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+
+ stream = (GUnixOutputStream*)g_unix_output_stream_new (fd, FALSE);
+ if (!g_output_stream_write_all ((GOutputStream*)stream,
+ g_variant_get_data (serialized),
+ g_variant_get_size (serialized),
+ &bytes_written,
+ NULL,
+ error))
+ goto out;
+ if (!g_output_stream_close ((GOutputStream*)stream,
+ NULL, error))
+ goto out;
+
+ if (!link_one_file (self, tmp_name, FALSE, FALSE, out_checksum, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ /* Unconditionally unlink; if we suceeded, there's a new link, if not, clean up. */
+ (void) unlink (tmp_name);
+ if (fd != -1)
+ close (fd);
+ if (serialized != NULL)
+ g_variant_unref (serialized);
+ g_free (tmp_name);
+ g_clear_object (&stream);
+ return ret;
+}
+
+static gboolean
+load_gvariant_object (HacktreeRepo *self,
+ HacktreeSerializedVariantType expected_type,
+ const char *sha256,
+ GVariant **out_variant,
+ GError **error)
+{
+ HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+ GMappedFile *mfile = NULL;
+ gboolean ret = FALSE;
+ GVariant *ret_variant = NULL;
+ GVariant *container = NULL;
+ char *path = NULL;
+
+ path = get_object_path_for_checksum (self, sha256);
+
+ mfile = g_mapped_file_new (priv->index_path, FALSE, error);
+ if (mfile == NULL)
+ goto out;
+ else
+ {
+ guint32 type;
+ container = g_variant_new_from_data (G_VARIANT_TYPE (HACKTREE_SERIALIZED_VARIANT_FORMAT),
+ g_mapped_file_get_contents (mfile),
+ g_mapped_file_get_length (mfile),
+ FALSE,
+ (GDestroyNotify) g_mapped_file_unref,
+ mfile);
+ if (!g_variant_is_of_type (container, G_VARIANT_TYPE (HACKTREE_SERIALIZED_VARIANT_FORMAT)))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Corrupted metadata object '%s'", sha256);
+ goto out;
+ }
+ g_variant_get (container, "(uv)",
+ &type, &ret_variant);
+ if (type != expected_type)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Corrupted metadata object '%s'; found type %u, expected %u", sha256,
+ type, (guint32)expected_type);
+ goto out;
+
+ }
+ mfile = NULL;
+ }
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ {
+ if (!ret_variant)
+ g_variant_unref (ret_variant);
+ }
+ else
+ *out_variant = ret_variant;
+ if (container != NULL)
+ g_variant_unref (container);
+ g_free (path);
+ if (mfile != NULL)
+ g_mapped_file_unref (mfile);
+ return ret;
+}
+
+static gboolean
+import_directory_meta (HacktreeRepo *self,
+ const char *path,
+ GVariant **out_variant,
+ GChecksum **out_checksum,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ struct stat stbuf;
+ GChecksum *ret_checksum = NULL;
+ GVariant *dirmeta = NULL;
+ char *xattrs = NULL;
+ gsize xattr_len;
+
+ if (lstat (path, &stbuf) < 0)
+ {
+ ht_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+
+ if (!S_ISDIR(stbuf.st_mode))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Not a directory: '%s'", path);
+ goto out;
+ }
+
+ if (!hacktree_get_xattrs_for_directory (path, &xattrs, &xattr_len, error))
+ goto out;
+
+ dirmeta = g_variant_new ("(uuuu ay)",
+ HACKTREE_DIR_META_VERSION,
+ (guint32)stbuf.st_uid,
+ (guint32)stbuf.st_gid,
+ (guint32)(stbuf.st_mode & ~S_IFMT),
+ g_variant_new_fixed_array (G_VARIANT_TYPE ("y"),
+ xattrs, xattr_len, 1));
+ g_variant_ref_sink (dirmeta);
+
+ if (!import_gvariant_object (self, HACKTREE_SERIALIZED_DIRMETA_VARIANT,
+ dirmeta, &ret_checksum, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ {
+ if (ret_checksum)
+ g_checksum_free (ret_checksum);
+ if (dirmeta != NULL)
+ g_variant_unref (dirmeta);
+ }
+ else
+ {
+ *out_checksum = ret_checksum;
+ *out_variant = dirmeta;
+ }
+ g_free (xattrs);
+ return ret;
+}
+
+static char *
+get_object_path_for_checksum (HacktreeRepo *self,
+ const char *checksum)
+{
+ HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+ char *checksum_prefix;
+ char *ret;
+
+ checksum_prefix = g_strndup (checksum, 2);
+ ret = g_build_filename (priv->objects_path, checksum_prefix, checksum + 2, NULL);
+ g_free (checksum_prefix);
+
+ return ret;
+}
static char *
prepare_dir_for_checksum_get_object_path (HacktreeRepo *self,
GChecksum *checksum,
GError **error)
{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
- char *checksum_prefix = NULL;
char *checksum_dir = NULL;
char *object_path = NULL;
- GError *temp_error = NULL;
- checksum_prefix = g_strndup (g_checksum_get_string (checksum), 2);
- g_assert_cmpuint (strlen (checksum_prefix), ==, 2);
- checksum_dir = g_build_filename (priv->objects_path, checksum_prefix, NULL);
+ object_path = get_object_path_for_checksum (self, g_checksum_get_string (checksum));
+ checksum_dir = g_path_get_dirname (object_path);
if (!ht_util_ensure_directory (checksum_dir, FALSE, error))
goto out;
- object_path = g_build_filename (checksum_dir, g_checksum_get_string (checksum) + 2, NULL);
out:
- g_free (checksum_prefix);
g_free (checksum_dir);
return object_path;
}
static gboolean
link_one_file (HacktreeRepo *self, const char *path,
- gboolean ignore_exists, gboolean force, GError **error)
+ gboolean ignore_exists, gboolean force,
+ GChecksum **out_checksum,
+ GError **error)
{
- HacktreeRepoPrivate *priv = GET_PRIVATE (self);
char *src_basename = NULL;
char *src_dirname = NULL;
char *dest_basename = NULL;
@@ -207,10 +448,8 @@ link_one_file (HacktreeRepo *self, const char *path,
DIR *src_dir = NULL;
DIR *dest_dir = NULL;
gboolean ret = FALSE;
- int fd;
struct stat stbuf;
char *dest_path = NULL;
- char *checksum_prefix;
src_basename = g_path_get_basename (path);
src_dirname = g_path_get_dirname (path);
@@ -265,6 +504,8 @@ link_one_file (HacktreeRepo *self, const char *path,
(void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
}
+ *out_checksum = id;
+ id = NULL;
ret = TRUE;
out:
if (id != NULL)
@@ -289,14 +530,661 @@ hacktree_repo_link_file (HacktreeRepo *self,
GError **error)
{
HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+ GChecksum *checksum = NULL;
+
+ g_return_val_if_fail (priv->inited, FALSE);
+
+ if (!link_one_file (self, path, ignore_exists, force, &checksum, error))
+ return FALSE;
+ g_checksum_free (checksum);
+ return TRUE;
+}
+
+typedef struct _ParsedTreeData ParsedTreeData;
+typedef struct _ParsedDirectoryData ParsedDirectoryData;
+
+static void parsed_tree_data_free (ParsedTreeData *pdata);
+
+struct _ParsedDirectoryData {
+ ParsedTreeData *tree_data;
+ char *metadata_sha256;
+ GVariant *meta_data;
+};
+
+static void
+parsed_directory_data_free (ParsedDirectoryData *pdata)
+{
+ if (pdata == NULL)
+ return;
+ parsed_tree_data_free (pdata->tree_data);
+ g_free (pdata->metadata_sha256);
+ g_variant_unref (pdata->meta_data);
+ g_free (pdata);
+}
+
+struct _ParsedTreeData {
+ GHashTable *files; /* char* filename -> char* checksum */
+ GHashTable *directories; /* char* dirname -> ParsedDirectoryData* */
+};
+
+static ParsedTreeData *
+parsed_tree_data_new (void)
+{
+ ParsedTreeData *ret = g_new0 (ParsedTreeData, 1);
+ ret->files = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)g_free);
+ ret->directories = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)parsed_directory_data_free);
+ return ret;
+}
+
+static void
+parsed_tree_data_free (ParsedTreeData *pdata)
+{
+ if (pdata == NULL)
+ return;
+ g_hash_table_destroy (pdata->files);
+ g_hash_table_destroy (pdata->directories);
+ g_free (pdata);
+}
+
+static gboolean
+parse_tree (HacktreeRepo *self,
+ const char *sha256,
+ ParsedTreeData **out_pdata,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ ParsedTreeData *ret_pdata = NULL;
+ int i, n;
+ guint32 version;
+ GVariant *tree_variant = NULL;
+ GVariant *meta_variant = NULL;
+ GVariant *files_variant = NULL;
+ GVariant *dirs_variant = NULL;
+
+ if (!load_gvariant_object (self, HACKTREE_SERIALIZED_TREE_VARIANT,
+ sha256, &tree_variant, error))
+ goto out;
+
+ g_variant_get (tree_variant, "(u a{sv}@a(ss)@a(sss))",
+ &version, &meta_variant, &files_variant, &dirs_variant);
+
+ ret_pdata = parsed_tree_data_new ();
+ n = g_variant_n_children (files_variant);
+ for (i = 0; i < n; i++)
+ {
+ const char *filename;
+ const char *checksum;
+
+ g_variant_get_child (files_variant, i, "(ss)", &filename, &checksum);
+
+ g_hash_table_insert (ret_pdata->files, g_strdup (filename), g_strdup (checksum));
+ }
+
+ n = g_variant_n_children (dirs_variant);
+ for (i = 0; i < n; i++)
+ {
+ const char *dirname;
+ const char *tree_checksum;
+ const char *meta_checksum;
+ ParsedTreeData *child_tree = NULL;
+ GVariant *metadata = NULL;
+ ParsedDirectoryData *child_dir = NULL;
+
+ g_variant_get_child (files_variant, i, "(sss)",
+ &dirname, &tree_checksum, &meta_checksum);
+
+ if (!parse_tree (self, tree_checksum, &child_tree, error))
+ goto out;
+
+ if (!load_gvariant_object (self, HACKTREE_SERIALIZED_DIRMETA_VARIANT,
+ meta_checksum, &metadata, error))
+ {
+ parsed_tree_data_free (child_tree);
+ goto out;
+ }
+
+ child_dir = g_new0 (ParsedDirectoryData, 1);
+ child_dir->tree_data = child_tree;
+ child_dir->metadata_sha256 = g_strdup (meta_checksum);
+ child_dir->meta_data = g_variant_ref_sink (metadata);
+
+ g_hash_table_insert (ret_pdata->directories, g_strdup (dirname), child_dir);
+ }
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ parsed_tree_data_free (ret_pdata);
+ else
+ *out_pdata = ret_pdata;
+ if (tree_variant)
+ g_variant_unref (tree_variant);
+ if (meta_variant)
+ g_variant_unref (meta_variant);
+ if (files_variant)
+ g_variant_unref (files_variant);
+ if (dirs_variant)
+ g_variant_unref (dirs_variant);
+ return ret;
+}
+
+static gboolean
+load_commit_and_trees (HacktreeRepo *self,
+ const char *commit_sha256,
+ GVariant **out_commit,
+ ParsedTreeData **out_tree_data,
+ GError **error)
+{
+ HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+ GVariant *ret_commit = NULL;
+ ParsedTreeData *ret_tree_data = NULL;
+ gboolean ret = FALSE;
+ const char *tree_checksum;
+
+ if (!priv->current_head)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Can't load current commit; no HEAD reference");
+ goto out;
+ }
+
+ if (!load_gvariant_object (self, HACKTREE_SERIALIZED_COMMIT_VARIANT,
+ commit_sha256, &ret_commit, error))
+ goto out;
+
+ g_variant_get_child (ret_commit, 5, "&s", &tree_checksum);
+
+ if (!parse_tree (self, tree_checksum, &ret_tree_data, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ {
+ if (ret_commit)
+ g_variant_unref (ret_commit);
+ parsed_tree_data_free (ret_tree_data);
+ }
+ else
+ {
+ *out_commit = ret_commit;
+ *out_tree_data = ret_tree_data;
+ }
+ return FALSE;
+}
+
+static GVariant *
+create_empty_gvariant_dict (void)
+{
+ GVariantBuilder builder;
+ g_variant_builder_init (&builder, G_VARIANT_TYPE("a{sv}"));
+ return g_variant_builder_end (&builder);
+}
+
+static gboolean
+import_parsed_tree (HacktreeRepo *self,
+ ParsedTreeData *tree,
+ GChecksum **out_checksum,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GVariant *serialized_tree = NULL;
+ gboolean builders_initialized = FALSE;
+ GVariantBuilder files_builder;
+ GVariantBuilder dirs_builder;
+ GHashTableIter hash_iter;
+ gpointer key, value;
+
+ g_variant_builder_init (&files_builder, G_VARIANT_TYPE ("a(ss)"));
+ g_variant_builder_init (&dirs_builder, G_VARIANT_TYPE ("a(sss)"));
+ builders_initialized = TRUE;
+
+ g_hash_table_iter_init (&hash_iter, tree->files);
+ while (g_hash_table_iter_next (&hash_iter, &key, &value))
+ {
+ const char *name = key;
+ const char *checksum = value;
+
+ g_variant_builder_add (&files_builder, "(ss)", name, checksum);
+ }
+
+ g_hash_table_iter_init (&hash_iter, tree->directories);
+ while (g_hash_table_iter_next (&hash_iter, &key, &value))
+ {
+ const char *name = key;
+ GChecksum *dir_checksum = NULL;
+ ParsedDirectoryData *dir = value;
+
+ if (!import_parsed_tree (self, dir->tree_data, &dir_checksum, error))
+ goto out;
+
+ g_variant_builder_add (&dirs_builder, "(sss)",
+ name, g_checksum_get_string (dir_checksum), dir->metadata_sha256);
+ }
+
+ serialized_tree = g_variant_new ("(u a{sv}@a(ss)@a(sss))",
+ 0,
+ create_empty_gvariant_dict (),
+ g_variant_builder_end (&files_builder),
+ g_variant_builder_end (&dirs_builder));
+ builders_initialized = FALSE;
+ g_variant_ref_sink (serialized_tree);
+ if (!import_gvariant_object (self, HACKTREE_SERIALIZED_TREE_VARIANT, serialized_tree, out_checksum, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (builders_initialized)
+ {
+ g_variant_builder_clear (&files_builder);
+ g_variant_builder_clear (&dirs_builder);
+ }
+ if (serialized_tree)
+ g_variant_unref (serialized_tree);
+ return ret;
+}
+
+static gboolean
+check_path (const char *filename,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ if (!*filename)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Invalid empty filename");
+ goto out;
+ }
+
+ if (ht_util_filename_has_dotdot (filename))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Path uplink '..' in filename '%s' not allowed (yet)", filename);
+ goto out;
+ }
+
+ if (g_path_is_absolute (filename))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Absolute filename '%s' not allowed (yet)", filename);
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+walk_parsed_tree (HacktreeRepo *self,
+ const char *filename,
+ ParsedTreeData *tree,
+ int *out_filename_index, /* out*/
+ char **out_component, /* out, but do not free */
+ ParsedTreeData **out_tree, /* out, but do not free */
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GPtrArray *components = NULL;
+ ParsedTreeData *current_tree = tree;
+ const char *component = NULL;
+ const char *file_sha1;
+ ParsedDirectoryData *dir;
+ int i;
+ int ret_filename_index = 0;
+
+ components = ht_util_path_split (filename);
+ g_assert (components != NULL);
+
+ current_tree = tree;
+ for (i = 0; i < components->len - 1; i++)
+ {
+ component = components->pdata[i];
+ file_sha1 = g_hash_table_lookup (current_tree->files, component);
+ dir = g_hash_table_lookup (current_tree->directories, component);
+
+ if (!(file_sha1 || dir))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No such file or directory: %s",
+ filename);
+ goto out;
+ }
+ else if (file_sha1)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Encountered non-directory '%s' in '%s'",
+ (char*)component,
+ filename);
+ goto out;
+ }
+ else if (!dir)
+ g_assert_not_reached ();
+ current_tree = dir->tree_data;
+ ret_filename_index++;
+ }
+
+ ret = TRUE;
+ g_assert (!(file_sha1 && dir));
+ *out_filename_index = i;
+ *out_component = components->pdata[i-1];
+ *out_tree = current_tree;
+ out:
+ g_ptr_array_free (components, TRUE);
+ return ret;
+}
+
+static gboolean
+remove_files_from_tree (HacktreeRepo *self,
+ const char *base,
+ GPtrArray *removed_files,
+ ParsedTreeData *tree,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int i;
+
+ for (i = 0; i < removed_files->len; i++)
+ {
+ const char *filename = removed_files->pdata[i];
+ int filename_index;
+ const char *component;
+ ParsedTreeData *parent;
+ const char *file_sha1;
+ ParsedTreeData *dir;
+
+ if (!check_path (filename, error))
+ goto out;
+
+ if (!walk_parsed_tree (self, filename, tree,
+ &filename_index, (char**)&component, &parent,
+ error))
+ goto out;
+
+ file_sha1 = g_hash_table_lookup (parent->files, component);
+ dir = g_hash_table_lookup (parent->directories, component);
+
+ if (file_sha1)
+ g_hash_table_remove (parent->files, component);
+ else if (dir)
+ g_hash_table_remove (parent->directories, component);
+ else
+ g_assert_not_reached ();
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+add_one_directory_to_tree_and_import (HacktreeRepo *self,
+ const char *basename,
+ const char *abspath,
+ ParsedTreeData *tree,
+ ParsedDirectoryData *dir,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GVariant *dirmeta = NULL;
+ GChecksum *dir_meta_checksum = NULL;
+
+ g_assert (tree != NULL);
+
+ if (!import_directory_meta (self, abspath, &dirmeta, &dir_meta_checksum, error))
+ goto out;
+
+ if (dir)
+ {
+ g_variant_unref (dir->meta_data);
+ dir->meta_data = dirmeta;
+ }
+ else
+ {
+ dir = g_new0 (ParsedDirectoryData, 1);
+ dir->tree_data = parsed_tree_data_new ();
+ dir->metadata_sha256 = g_strdup (g_checksum_get_string (dir_meta_checksum));
+ dir->meta_data = dirmeta;
+ g_hash_table_insert (tree->directories, g_strdup (basename), dir);
+ }
+
+ ret = TRUE;
+ out:
+ if (dir_meta_checksum)
+ g_checksum_free (dir_meta_checksum);
+ return ret;
+}
+
+static gboolean
+add_one_file_to_tree_and_import (HacktreeRepo *self,
+ const char *basename,
+ const char *abspath,
+ ParsedTreeData *tree,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GChecksum *checksum = NULL;
+
+ g_assert (tree != NULL);
+
+ if (!link_one_file (self, abspath, TRUE, FALSE, &checksum, error))
+ goto out;
+
+ g_hash_table_replace (tree->files, g_strdup (basename),
+ g_strdup (g_checksum_get_string (checksum)));
+
+ ret = TRUE;
+ out:
+ if (checksum)
+ g_checksum_free (checksum);
+ return ret;
+}
+
+static gboolean
+add_one_path_to_tree_and_import (HacktreeRepo *self,
+ const char *base,
+ const char *filename,
+ ParsedTreeData *tree,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GPtrArray *components = NULL;
+ struct stat stbuf;
+ char *component_abspath = NULL;
+ ParsedTreeData *current_tree = tree;
+ const char *component = NULL;
+ const char *file_sha1;
+ char *abspath = NULL;
+ ParsedDirectoryData *dir;
+ int i;
+ gboolean is_directory;
+
+ if (!check_path (filename, error))
+ goto out;
+
+ abspath = g_build_filename (base, filename, NULL);
+
+ if (lstat (abspath, &stbuf) < 0)
+ {
+ ht_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+ is_directory = S_ISDIR(stbuf.st_mode);
+
+ if (components)
+ g_ptr_array_free (components, TRUE);
+ components = ht_util_path_split (filename);
+ g_assert (components->len > 0);
+
+ current_tree = tree;
+ for (i = 0; i < components->len; i++)
+ {
+ component = components->pdata[i];
+ g_free (component_abspath);
+ component_abspath = ht_util_path_join_n (base, components, i);
+ file_sha1 = g_hash_table_lookup (current_tree->files, component);
+ dir = g_hash_table_lookup (current_tree->directories, component);
+
+ if (i < components->len - 1)
+ {
+ if (file_sha1 != NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Encountered non-directory '%s' in '%s'",
+ component,
+ filename);
+ goto out;
+ }
+ /* Implicitly add intermediate directories */
+ if (!add_one_directory_to_tree_and_import (self, component,
+ abspath, current_tree, dir,
+ error))
+ goto out;
+ }
+ else if (is_directory)
+ {
+ if (file_sha1 != NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "File '%s' can't be overwritten by directory",
+ filename);
+ goto out;
+ }
+ if (!add_one_directory_to_tree_and_import (self, component,
+ abspath, current_tree, dir,
+ error))
+ goto out;
+ }
+ else
+ {
+ g_assert (!is_directory);
+ if (dir != NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "File '%s' can't be overwritten by directory",
+ filename);
+ goto out;
+ }
+ if (!add_one_file_to_tree_and_import (self, component, abspath,
+ current_tree, error))
+ goto out;
+ }
+ }
+
+ ret = TRUE;
+ out:
+ g_free (component_abspath);
+ g_free (abspath);
+ return ret;
+}
+
+static gboolean
+add_files_to_tree_and_import (HacktreeRepo *self,
+ const char *base,
+ GPtrArray *added_files,
+ ParsedTreeData *tree,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int i;
+
+ for (i = 0; i < added_files->len; i++)
+ {
+ const char *path = added_files->pdata[i];
+
+ if (!add_one_path_to_tree_and_import (self, base, path, tree, error))
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+gboolean
+hacktree_repo_commit (HacktreeRepo *self,
+ const char *subject,
+ const char *body,
+ GVariant *metadata,
+ const char *base,
+ GPtrArray *modified_files,
+ GPtrArray *removed_files,
+ GChecksum **out_commit,
+ GError **error)
+{
+ HacktreeRepoPrivate *priv = GET_PRIVATE (self);
+ gboolean ret = FALSE;
+ ParsedTreeData *tree = NULL;
+ GVariant *previous_commit = NULL;
+ GVariant *commit = NULL;
+ GChecksum *root_checksum = NULL;
+ GChecksum *ret_commit_checksum = NULL;
+ GDateTime *now = NULL;
g_return_val_if_fail (priv->inited, FALSE);
- return link_one_file (self, path, ignore_exists, force, error);
+ if (priv->current_head)
+ {
+ if (!load_commit_and_trees (self, priv->current_head, &previous_commit, &tree, error))
+ goto out;
+ }
+ else
+ {
+ /* Initial commit */
+ tree = parsed_tree_data_new ();
+ }
+
+ if (!remove_files_from_tree (self, base, removed_files, tree, error))
+ goto out;
+
+ if (!add_files_to_tree_and_import (self, base, modified_files, tree, error))
+ goto out;
+
+ if (!import_parsed_tree (self, tree, &root_checksum, error))
+ goto out;
+
+ now = g_date_time_new_now_utc ();
+ commit = g_variant_new ("(u a{sv}sssts)",
+ HACKTREE_COMMIT_VERSION,
+ create_empty_gvariant_dict (),
+ priv->current_head ? priv->current_head : "",
+ subject, body,
+ g_date_time_to_unix (now) / G_TIME_SPAN_SECOND,
+ g_checksum_get_string (root_checksum));
+ if (!import_gvariant_object (self, HACKTREE_SERIALIZED_COMMIT_VARIANT,
+ commit, &ret_commit_checksum, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ {
+ if (ret_commit_checksum)
+ g_checksum_free (ret_commit_checksum);
+ }
+ else
+ {
+ *out_commit = ret_commit_checksum;
+ }
+ if (root_checksum)
+ g_checksum_free (root_checksum);
+ if (previous_commit)
+ g_variant_unref (previous_commit);
+ parsed_tree_data_free (tree);
+ if (commit)
+ g_variant_unref (commit);
+ if (now)
+ g_date_time_unref (now);
+ return ret;
}
static gboolean
-iter_object_dir (HacktreeRepo *repo,
+iter_object_dir (HacktreeRepo *self,
GFile *dir,
HacktreeRepoObjectIter callback,
gpointer user_data,
@@ -328,7 +1216,7 @@ iter_object_dir (HacktreeRepo *repo,
if (strlen (name) == 62 && type != G_FILE_TYPE_DIRECTORY)
{
char *path = g_build_filename (dirpath, name, NULL);
- callback (repo, path, file_info, user_data);
+ callback (self, path, file_info, user_data);
g_free (path);
}
diff --git a/src/libhacktree/hacktree-repo.h b/src/libhacktree/hacktree-repo.h
index 633771c..55da5ee 100644
--- a/src/libhacktree/hacktree-repo.h
+++ b/src/libhacktree/hacktree-repo.h
@@ -59,6 +59,16 @@ gboolean hacktree_repo_link_file (HacktreeRepo *repo,
gboolean force,
GError **error);
+gboolean hacktree_repo_commit (HacktreeRepo *repo,
+ const char *subject,
+ const char *body,
+ GVariant *metadata,
+ const char *base,
+ GPtrArray *modified_files,
+ GPtrArray *removed_files,
+ GChecksum **out_commit,
+ GError **error);
+
typedef void (*HacktreeRepoObjectIter) (HacktreeRepo *repo, const char *path,
GFileInfo *fileinfo, gpointer user_data);
diff --git a/src/libhtutil/ht-gio-utils.c b/src/libhtutil/ht-gio-utils.c
index 29f193c..615699a 100644
--- a/src/libhtutil/ht-gio-utils.c
+++ b/src/libhtutil/ht-gio-utils.c
@@ -23,10 +23,11 @@
#include <glib-unix.h>
#include <gio/gio.h>
+#include <gio/gunixinputstream.h>
#include <string.h>
-#include "ht-gio-utils.h"
+#include "htutil.h"
gboolean
ht_util_ensure_directory (const char *path, gboolean with_parents, GError **error)
@@ -56,3 +57,51 @@ ht_util_ensure_directory (const char *path, gboolean with_parents, GError **erro
g_clear_object (&dir);
return ret;
}
+
+
+char *
+ht_util_get_file_contents_utf8 (const char *path,
+ GError **error)
+{
+ char *contents;
+ gsize len;
+ if (!g_file_get_contents (path, &contents, &len, error))
+ return NULL;
+ if (!g_utf8_validate (contents, len, NULL))
+ {
+ g_free (contents);
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "File %s contains invalid UTF-8",
+ path);
+ return NULL;
+ }
+ return contents;
+}
+
+GInputStream *
+ht_util_read_file_noatime (GFile *file, GCancellable *cancellable, GError **error)
+{
+ GInputStream *ret = NULL;
+ int fd;
+ int flags = O_RDONLY;
+ char *path = NULL;
+
+ path = g_file_get_path (file);
+#ifdef O_NOATIME
+ flags |= O_NOATIME;
+#endif
+ fd = open (path, flags);
+ if (fd < 0)
+ {
+ ht_util_set_error_from_errno (error, errno);
+ goto out;
+ }
+
+ ret = (GInputStream*)g_unix_input_stream_new (fd, TRUE);
+
+ out:
+ g_free (path);
+ return ret;
+}
diff --git a/src/libhtutil/ht-gio-utils.h b/src/libhtutil/ht-gio-utils.h
index 5b0c65c..388a654 100644
--- a/src/libhtutil/ht-gio-utils.h
+++ b/src/libhtutil/ht-gio-utils.h
@@ -19,8 +19,8 @@
* Author: Colin Walters <walters verbum org>
*/
-#ifndef __HACKTREE_UNIX_UTILS_H__
-#define __HACKTREE_UNIX_UTILS_H__
+#ifndef __HACKTREE_GIO_UTILS_H__
+#define __HACKTREE_GIO_UTILS_H__
#include <gio/gio.h>
@@ -28,6 +28,10 @@ G_BEGIN_DECLS
gboolean ht_util_ensure_directory (const char *path, gboolean with_parents, GError **error);
+char * ht_util_get_file_contents_utf8 (const char *path, GError **error);
+
+GInputStream *ht_util_read_file_noatime (GFile *file, GCancellable *cancellable, GError **error);
+
G_END_DECLS
#endif
diff --git a/src/libhtutil/ht-unix-utils.c b/src/libhtutil/ht-unix-utils.c
index 4637c7d..15ce374 100644
--- a/src/libhtutil/ht-unix-utils.c
+++ b/src/libhtutil/ht-unix-utils.c
@@ -30,6 +30,125 @@
#include <sys/types.h>
#include <dirent.h>
+static int
+compare_filenames_by_component_length (const char *a,
+ const char *b)
+{
+ char *a_slash, *b_slash;
+
+ a_slash = strchr (a, '/');
+ b_slash = strchr (b, '/');
+ while (a_slash && b_slash)
+ {
+ a = a_slash + 1;
+ b = b_slash + 1;
+ a_slash = strchr (a, '/');
+ b_slash = strchr (b, '/');
+ }
+ if (a_slash)
+ return -1;
+ else if (b_slash)
+ return 1;
+ else
+ return 0;
+}
+
+GPtrArray *
+ht_util_sort_filenames_by_component_length (GPtrArray *files)
+{
+ GPtrArray *array = g_ptr_array_sized_new (files->len);
+ memcpy (array->pdata, files->pdata, sizeof (gpointer) * files->len);
+ g_ptr_array_sort (array, (GCompareFunc) compare_filenames_by_component_length);
+ return array;
+}
+
+int
+ht_util_count_filename_components (const char *path)
+{
+ int i = 0;
+
+ while (path)
+ {
+ i++;
+ path = strchr (path, '/');
+ if (path)
+ path++;
+ }
+ return i;
+}
+
+gboolean
+ht_util_filename_has_dotdot (const char *path)
+{
+ char *p;
+ char last;
+
+ if (strcmp (path, "..") == 0)
+ return TRUE;
+ if (g_str_has_prefix (path, "../"))
+ return TRUE;
+ p = strstr (path, "/..");
+ if (!p)
+ return FALSE;
+ last = *(p + 1);
+ return last == '\0' || last == '/';
+}
+
+GPtrArray *
+ht_util_path_split (const char *path)
+{
+ GPtrArray *ret = NULL;
+ const char *p;
+ const char *slash;
+
+ g_return_val_if_fail (path[0] != '/', NULL);
+
+ ret = g_ptr_array_new ();
+ g_ptr_array_set_free_func (ret, g_free);
+
+ p = path;
+ do {
+ slash = strchr (p, '/');
+ if (!slash)
+ {
+ g_ptr_array_add (ret, g_strdup (p));
+ p = NULL;
+ }
+ else
+ {
+ g_ptr_array_add (ret, g_strndup (p, slash - p));
+ p += 1;
+ }
+ } while (p);
+
+ return ret;
+}
+
+char *
+ht_util_path_join_n (const char *base, GPtrArray *components, int n)
+{
+ int max = MAX(n+1, components->len);
+ GPtrArray *subcomponents;
+ char *path;
+ int i;
+
+ subcomponents = g_ptr_array_new ();
+
+ if (base != NULL)
+ g_ptr_array_add (subcomponents, (char*)base);
+
+ for (i = 0; i < max; i++)
+ {
+ g_ptr_array_add (subcomponents, components->pdata[i]);
+ }
+ g_ptr_array_add (subcomponents, NULL);
+
+ path = g_build_filenamev ((char**)subcomponents->pdata);
+ g_ptr_array_free (subcomponents, TRUE);
+
+ return path;
+}
+
void
ht_util_set_error_from_errno (GError **error,
gint saved_errno)
diff --git a/src/libhtutil/ht-unix-utils.h b/src/libhtutil/ht-unix-utils.h
index ae3731b..bd95b55 100644
--- a/src/libhtutil/ht-unix-utils.h
+++ b/src/libhtutil/ht-unix-utils.h
@@ -31,9 +31,21 @@
#include <unistd.h>
#include <dirent.h>
#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
G_BEGIN_DECLS
+gboolean ht_util_filename_has_dotdot (const char *path);
+
+GPtrArray *ht_util_sort_filenames_by_component_length (GPtrArray *files);
+
+GPtrArray* ht_util_path_split (const char *path);
+
+char *ht_util_path_join_n (const char *base, GPtrArray *components, int n);
+
+int ht_util_count_filename_components (const char *path);
+
int ht_util_open_file_read (const char *path, GError **error);
int ht_util_open_file_read_at (int dirfd, const char *name, GError **error);
diff --git a/src/main.c b/src/main.c
index d39ef88..6428a1b 100644
--- a/src/main.c
+++ b/src/main.c
@@ -30,6 +30,7 @@
static HacktreeBuiltin builtins[] = {
{ "init", hacktree_builtin_init, 0 },
+ { "commit", hacktree_builtin_commit, 0 },
{ "link-file", hacktree_builtin_link_file, 0 },
{ "fsck", hacktree_builtin_fsck, 0 },
{ NULL }
diff --git a/tests/t0002-commit-one.sh b/tests/t0002-commit-one.sh
new file mode 100755
index 0000000..a8f8d2e
--- /dev/null
+++ b/tests/t0002-commit-one.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+#
+# Copyright (C) 2011 Colin Walters <walters verbum org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Author: Colin Walters <walters verbum org>
+
+set -e
+
+. libtest.sh
+
+echo '1..3'
+
+mkdir files
+cd files
+echo hello > yy
+
+mkdir ../repo
+repo="--repo=../repo"
+hacktree init $repo
+echo 'ok init'
+hacktree commit $repo -s "Test Commit" -b "Commit body" --add=yy
+echo 'ok commit'
+hacktree fsck -q $repo
+echo 'ok fsck'
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]