diff --git a/Makefile-hacktree.am b/Makefile-hacktree.am index d3e757d9..3dfa8cac 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-checkout.c \ src/ht-builtin-commit.c \ src/ht-builtin-fsck.c \ src/ht-builtin-init.c \ diff --git a/src/ht-builtin-checkout.c b/src/ht-builtin-checkout.c new file mode 100644 index 00000000..5ed32b97 --- /dev/null +++ b/src/ht-builtin-checkout.c @@ -0,0 +1,81 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * 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 + */ + +#include "config.h" + +#include "ht-builtins.h" +#include "hacktree.h" + +#include + +static char *repo_path; + +static GOptionEntry options[] = { + { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" }, + { NULL } +}; + +gboolean +hacktree_builtin_checkout (int argc, char **argv, const char *prefix, GError **error) +{ + GOptionContext *context; + gboolean ret = FALSE; + HacktreeRepo *repo = NULL; + int i; + const char *commit; + const char *destination; + + context = g_option_context_new ("COMMIT DESTINATION - Check out a commit into a filesystem tree"); + 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 = "."; + + repo = hacktree_repo_new (repo_path); + if (!hacktree_repo_check (repo, error)) + goto out; + + if (argc < 3) + { + gchar *help = g_option_context_get_help (context, TRUE, NULL); + g_printerr ("%s\n", help); + g_free (help); + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "COMMIT and DESTINATION must be specified"); + goto out; + } + + commit = argv[1]; + destination = argv[2]; + + if (!hacktree_repo_checkout (repo, commit, destination, error)) + goto out; + + ret = TRUE; + out: + if (context) + g_option_context_free (context); + g_clear_object (&repo); + return ret; +} diff --git a/src/ht-builtins.h b/src/ht-builtins.h index da5a2866..e1db9c17 100644 --- a/src/ht-builtins.h +++ b/src/ht-builtins.h @@ -36,6 +36,7 @@ typedef struct { int flags; /* HacktreeBuiltinFlags */ } HacktreeBuiltin; +gboolean hacktree_builtin_checkout (int argc, char **argv, const char *prefix, GError **error); 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); diff --git a/src/libhacktree/hacktree-repo.c b/src/libhacktree/hacktree-repo.c index ae72545d..57fb4668 100644 --- a/src/libhacktree/hacktree-repo.c +++ b/src/libhacktree/hacktree-repo.c @@ -425,7 +425,7 @@ import_directory_meta (HacktreeRepo *self, HACKTREE_DIR_META_VERSION, (guint32)stbuf.st_uid, (guint32)stbuf.st_gid, - (guint32)(stbuf.st_mode & ~S_IFMT), + (guint32)(stbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)), g_variant_new_fixed_array (G_VARIANT_TYPE ("y"), xattrs, xattr_len, 1)); g_variant_ref_sink (dirmeta); @@ -1424,3 +1424,160 @@ hacktree_repo_get_head (HacktreeRepo *self) return priv->current_head; } + +static gboolean +resolve_ref (HacktreeRepo *self, + const char *ref, + char **resolved, + GError **error) +{ + if (strcmp (ref, "HEAD") == 0) + { + *resolved = g_strdup (hacktree_repo_get_head (self)); + return TRUE; + } + else if (strlen (ref) == 64) + { + *resolved = g_strdup (ref); + return TRUE; + } + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid ref '%s' (must be SHA256 or HEAD)", ref); + return FALSE; +} + + +static gboolean +checkout_tree (HacktreeRepo *self, + ParsedTreeData *tree, + const char *destination, + GError **error); + +static gboolean +checkout_one_directory (HacktreeRepo *self, + const char *destination, + const char *dirname, + ParsedDirectoryData *dir, + GError **error) +{ + gboolean ret = FALSE; + char *dest_path = NULL; + guint32 version, uid, gid, mode; + GVariant *xattr_variant = NULL; + const guint8 *xattrs = NULL; + gsize xattr_len; + + dest_path = g_build_filename (destination, dirname, NULL); + + g_variant_get (dir->meta_data, "(uuuu@ay)", + &version, &uid, &gid, &mode, + &xattr_variant); + xattrs = g_variant_get_fixed_array (xattr_variant, &xattr_len, 1); + + if (mkdir (dest_path, (mode_t)mode) < 0) + { + ht_util_set_error_from_errno (error, errno); + goto out; + } + + if (!checkout_tree (self, dir->tree_data, dest_path, error)) + goto out; + + /* TODO - xattrs */ + + ret = TRUE; + out: + g_free (dest_path); + g_variant_unref (xattr_variant); + return ret; +} + +static gboolean +checkout_tree (HacktreeRepo *self, + ParsedTreeData *tree, + const char *destination, + GError **error) +{ + gboolean ret = FALSE; + GHashTableIter hash_iter; + gpointer key, value; + + g_hash_table_iter_init (&hash_iter, tree->files); + while (g_hash_table_iter_next (&hash_iter, &key, &value)) + { + const char *filename = key; + const char *checksum = value; + char *object_path; + char *dest_path; + + object_path = get_object_path (self, checksum, HACKTREE_OBJECT_TYPE_FILE); + dest_path = g_build_filename (destination, filename, NULL); + if (link (object_path, dest_path) < 0) + { + ht_util_set_error_from_errno (error, errno); + g_free (object_path); + g_free (dest_path); + goto out; + } + g_free (object_path); + g_free (dest_path); + } + + g_hash_table_iter_init (&hash_iter, tree->directories); + while (g_hash_table_iter_next (&hash_iter, &key, &value)) + { + const char *dirname = key; + ParsedDirectoryData *dir = value; + + if (!checkout_one_directory (self, destination, dirname, dir, error)) + goto out; + } + + ret = TRUE; + out: + return ret; +} + +gboolean +hacktree_repo_checkout (HacktreeRepo *self, + const char *ref, + const char *destination, + GError **error) +{ + gboolean ret = FALSE; + GVariant *commit = NULL; + char *resolved = NULL; + ParsedTreeData *tree = NULL; + + if (g_file_test (destination, G_FILE_TEST_EXISTS)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Destination path '%s' already exists", + destination); + goto out; + } + + if (!resolve_ref (self, ref, &resolved, error)) + goto out; + + /* FIXME - perms etc on root directory */ + if (mkdir (destination, (mode_t)0755) < 0) + { + ht_util_set_error_from_errno (error, errno); + goto out; + } + + if (!load_commit_and_trees (self, resolved, &commit, &tree, error)) + goto out; + + if (!checkout_tree (self, tree, destination, error)) + goto out; + + ret = TRUE; + out: + g_free (resolved); + if (commit) + g_variant_unref (commit); + parsed_tree_data_free (tree); + return ret; +} diff --git a/src/libhacktree/hacktree-repo.h b/src/libhacktree/hacktree-repo.h index c401dd29..d9d0eeeb 100644 --- a/src/libhacktree/hacktree-repo.h +++ b/src/libhacktree/hacktree-repo.h @@ -78,7 +78,7 @@ gboolean hacktree_repo_commit (HacktreeRepo *self, GError **error); gboolean hacktree_repo_checkout (HacktreeRepo *self, - const char *commit, + const char *ref, const char *destination, GError **error); diff --git a/src/main.c b/src/main.c index c3cafb87..6ccb1589 100644 --- a/src/main.c +++ b/src/main.c @@ -29,6 +29,7 @@ #include "ht-builtins.h" static HacktreeBuiltin builtins[] = { + { "checkout", hacktree_builtin_checkout, 0 }, { "init", hacktree_builtin_init, 0 }, { "commit", hacktree_builtin_commit, 0 }, { "link-file", hacktree_builtin_link_file, 0 }, diff --git a/tests/libtest.sh b/tests/libtest.sh index 7fc8ed1f..1d565a5b 100644 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -32,4 +32,21 @@ die () { fi } +setup_test_repository1 () { + mkdir files + cd files + ht_files=`pwd` + export ht_files + echo first > firstfile + echo second > secondfile + + mkdir ../repo + ht_repo="--repo=../repo" + export ht_repo + hacktree init $ht_repo + hacktree commit $ht_repo -s "Test Commit 1" -b "Commit body first" --add=firstfile + hacktree commit $ht_repo -s "Test Commit 2" -b "Commit body first" --add=secondfile + hacktree fsck -q $ht_repo +} + trap 'die' EXIT diff --git a/tests/t0004-checkout-test1.sh b/tests/t0004-checkout-test1.sh new file mode 100755 index 00000000..1244ddad --- /dev/null +++ b/tests/t0004-checkout-test1.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# +# Copyright (C) 2011 Colin Walters +# +# 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 + +set -e + +. libtest.sh + +echo '1..3' + +setup_test_repository1 +echo 'ok setup' +hacktree checkout $ht_repo HEAD $test_tmpdir/checkout1-head +echo 'ok checkout cmd' +cd $test_tmpdir/checkout1-head +test -f firstfile +test -f secondfile +echo 'ok checkout verify exists' + +