8000 Hook support by tiennou · Pull Request #4620 · libgit2/libgit2 · GitHub
[go: up one dir, main page]

Skip to content

Hook support #4620

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
hooks: hook enumeration support
  • Loading branch information
tiennou committed Sep 10, 2020
commit b9ef618144fceecd8fb6f025c3cdfb30fff83c43
1 change: 1 addition & 0 deletions include/git2.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "git2/filter.h"
#include "git2/global.h"
#include "git2/graph.h"
#include "git2/hook.h"
#include "git2/ignore.h"
#include "git2/index.h"
#include "git2/indexer.h"
Expand Down
3 changes: 2 additions & 1 deletion include/git2/errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ typedef enum {
GIT_ERROR_WORKTREE,
GIT_ERROR_SHA1,
GIT_ERROR_HTTP,
GIT_ERROR_INTERNAL
GIT_ERROR_INTERNAL,
GIT_ERROR_HOOK
} git_error_t;

/**
Expand Down
50 changes: 50 additions & 0 deletions include/git2/hook.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_git_hook_h__
#define INCLUDE_git_hook_h__

#include "common.h"
#include "types.h"

/**
* @file git2/hook.h
* @brief Git hook management routines
* @defgroup git_hook Git hook management routines
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL

/**
* The callback used with git_hook_foreach.
*
* @see git_hook_foreach.
*
* @param hook_name The hook name.
* @param payload A user-specified pointer.
* @return 0 to continue looping, non-zero to stop.
*/
typedef int GIT_CALLBACK(git_hook_foreach_cb)(const char *hook_name, void *payload);

/**
* Enumerate a repository's hooks.
*
* The callback will be called with the name of each available hook.
*
* @param repo The repository.
* @param callback The enumeration callback.
* @param payload A user-provided pointer that will be passed to the callback.
* @return 0 on success, or an error code.
*/
GIT_EXTERN(int) git_hook_foreach(
git_repository *repo,
git_hook_foreach_cb callback,
void *payload);

/* @} */
GIT_END_DECL
#endif
37 changes: 37 additions & 0 deletions include/git2/sys/hook.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_sys_git_hook_h__
#define INCLUDE_sys_git_hook_h__

#include "git2/common.h"
#include "git2/types.h"
#include "git2/oid.h"

/**
* @file git2/sys/hook.h
* @brief Low-level Git hook calls
* @defgroup git_hook_call Low-level Git hook calls
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL

/**
* A helper to get the path of the repository's hooks.
*
* This will obey core.hooksPath.
*
* @param out_dir The absolute path to the hooks location.
* @param repo The repository to get the hooks location from.
* @return 0 on success, or an error code.
*/
GIT_EXTERN(int) git_hook_dir(git_buf *out_dir, git_repository *repo);

/** @} */
GIT_END_DECL

#endif
145 changes: 145 additions & 0 deletions src/hook.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/

#include "common.h"

#include "posix.h"
#include "repository.h"

#include "git2/hook.h"
#include "git2/sys/hook.h"


const char * const githooks[] = {
"applypatch-msg",
"pre-applypatch",
"post-applypatch",
"pre-commit",
"prepare-commit-msg",
"commit-msg",
"post-commit",
"pre-rebase",
"post-checkout",
"post-merge",
"pre-push",
"pre-receive",
"update",
"post-receive",
"post-update",
"push-to-checkout",
"pre-auto-gc",
"post-rewrite",
};

int git_hook_dir(git_buf *out_dir, git_repository *repo)
{
int err;
git_buf cfg_path = GIT_BUF_INIT;
git_config *cfg;

assert(out_dir && repo);

/* We need to check for an override in the repo config */
err = git_repository_config__weakptr(&cfg, repo);
if (err != 0)
return err;

err = git_config_get_path(&cfg_path, cfg, "core.hooksPath");
if (err == GIT_ENOTFOUND) {
git_error_clear();
if ((err = git_repository_item_path(out_dir, repo, GIT_REPOSITORY_ITEM_HOOKS)) < 0)
return err;
} else if (err == GIT_OK) {
git_buf_joinpath(out_dir, git_repository_commondir(repo), cfg_path.ptr);
git_path_resolve_relative(out_dir, 0);
git_buf_dispose(&cfg_path);
} else {
return err;
}

return 0;
}

static int build_hook_path(git_buf *out_path, git_repository *repo, const char *hook_name)
{
int err;
git_buf hook_path = GIT_BUF_INIT;

assert(hook_name);

err = git_hook_dir(&hook_path, repo);
if (err != 0)
return -1;

err = git_buf_joinpath(&hook_path, hook_path.ptr, hook_name);
if (err != 0)
return -1;

*out_path = hook_path;

return 0;
}

static int check_hook_path(const git_buf *hook_path)
{
int err;
struct stat hook_stat;

assert(hook_path);

/* Skip missing hooks */
err = p_stat(git_buf_cstr(hook_path), &hook_stat);
if (err == ENOENT) {
git_error_set(GIT_ERROR_HOOK, "hook %s wasn't found", git_buf_cstr(hook_path));
return GIT_ENOTFOUND;
} else if (err) {
git_error_set(GIT_ERROR_OS, "failed to stat %s", git_buf_cstr(hook_path));
return -1;
}

#ifndef GIT_WIN32
/* Check exec bits */
if ((hook_stat.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0) {
git_error_set(GIT_ERROR_HOOK, "can't exec hook %s", git_buf_cstr(hook_path));
return -1;
}
#endif

return 0;
}

int git_hook_foreach(
git_repository *repo,
git_hook_foreach_cb callback,
void *payload)
{
size_t hook_id = 0;

assert(repo && callback);

for (hook_id = 0; hook_id < ARRAY_SIZE(githooks); hook_id++) {
const char *hook_name = githooks[hook_id];
int err = 0;
git_buf hook_path = GIT_BUF_INIT;

err = build_hook_path(&hook_path, repo, hook_name);
if (err != 0)
continue;

err = check_hook_path(&hook_path);
git_buf_dispose(&hook_path);

if (err != 0)
continue;

err = callback(hook_name, payload);
if (err != 0)
break;
}

return 0;
}
19 changes: 19 additions & 0 deletions tests/clar_libgit2.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <git2.h>
#include "common.h"
#include "posix.h"
#include "vector.h"

/**
* Replace for `clar_must_pass` that passes the last library error as the
Expand Down Expand Up @@ -150,6 +151,24 @@ GIT_INLINE(void) clar__assert_equal_oid(
clar__assert_equal_oid(__FILE__, __func__, __LINE__, \
"OID mismatch: " #one " != " #two, (one), (two))

GIT_INLINE(void) clar__assert_equal_vector_str(
const char *file, const char *func, int line,
const git_vector *a, const git_vector *b)
{
size_t i;
char *string;

clar__assert_equal(file, func, line, "Vector length mismatch: %" PRIuZ " != %" PRIuZ, 1, PRIuZ, git_vector_length(a), git_vector_length(b));

git_vector_foreach(a, i, string) {
clar__assert_equal(file, func, line, "Vector element mismatch: %s != %s", 1, "%s", string, git_vector_get(b, i));
}
}

#define cl_assert_equal_v_str(a, b) \
clar__assert_equal_vector_str(__FILE__, __func__, __LINE__, \
(a), (b))

/*
* Some utility macros for building long strings
*/
Expand Down
102 changes: 102 additions & 0 deletions tests/hook/enumerate.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#include "clar_libgit2.h"
#include "git2/sys/hook.h"

static git_repository *g_repo = NULL;

void test_hook_enumerate__initialize(void)
{
g_repo = cl_git_sandbox_init("testrepo");
}

void test_hook_enumerate__cleanup(void)
{
cl_git_sandbox_cleanup();
g_repo = NULL;
}

static int test_git_hook_foreach_cb(const char *hook_name, void *payload)
{
git_vector *hook_list = payload;
git_vector_insert(hook_list, (char *)hook_name);
return 0;
}

void test_hook_enumerate__foreach_hooks(void)
{
git_vector hook_list = GIT_VECTOR_INIT;
git_vector expected_list = GIT_VECTOR_INIT;

git_vector_insert(&expected_list, "commit-msg");
git_vector_insert(&expected_list, "post-merge");

cl_git_pass(git_hook_foreach(g_repo, test_git_hook_foreach_cb, &hook_list));

cl_assert_equal_v_str(&hook_list, &expected_list);

git_vector_free(&hook_list);
git_vector_free(&expected_list);
}

static int test_git_hook_foreach_skip_cb(const char *hook_name, void *payload)
{
git_vector *hook_list = payload;
git_vector_insert(hook_list, (char *)hook_name);
return -1;
}

void test_hook_enumerate__foreach_skip(void)
{
git_vector hook_list = GIT_VECTOR_INIT;
git_vector expected_list = GIT_VECTOR_INIT;

git_vector_insert(&expected_list, "commit-msg");

cl_git_pass(git_hook_foreach(g_repo, test_git_hook_foreach_skip_cb, &hook_list));

cl_assert_equal_v_str(&hook_list, &expected_list);

git_vector_free(&hook_list);
git_vector_free(&expected_list);
}

#define ALT_HOOK_DIR "../testhooks"

void test_hook_enumerate__foreach_hooks_config_override(void)
{
git_config *cfg;
git_buf alt_hook_dir = GIT_BUF_INIT;
git_buf alt_hook = GIT_BUF_INIT;
git_vector hook_list = GIT_VECTOR_INIT;
git_vector expected_list = GIT_VECTOR_INIT;

/* Change the repository hooks */
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(git_config_set_string(cfg, "core.hooksPath", ALT_HOOK_DIR));

cl_git_pass(git_hook_dir(&alt_hook_dir, g_repo));

/* Setup an alternate hook directory */
cl_must_pass(p_mkdir(git_buf_cstr(&alt_hook_dir), 0777));

cl_git_pass(git_buf_joinpath(&alt_hook, alt_hook_dir.ptr, "commit-msg"));
cl_git_mkfile(git_buf_cstr(&alt_hook), NULL);
cl_must_pass(p_chmod(git_buf_cstr(&alt_hook), 0776));
git_buf_dispose(&alt_hook);

cl_git_pass(git_buf_joinpath(&alt_hook, alt_hook_dir.ptr, "post-merge"));
cl_git_mkfile(git_buf_cstr(&alt_hook), NULL);
cl_must_pass(p_chmod(git_buf_cstr(&alt_hook), 0776));
git_buf_dispose(&alt_hook);

/* Check that we get the correct hooks */
cl_must_pass(git_vector_insert(&expected_list, "commit-msg"));
cl_must_pass(git_vector_insert(&expected_list, "post-merge"));

cl_git_pass(git_hook_foreach(g_repo, test_git_hook_foreach_cb, &hook_list));

cl_assert_equal_v_str(&hook_list, &expected_list);

git_vector_free(&hook_list);
git_vector_free(&expected_list);
git_buf_dispose(&alt_hook_dir);
}
Empty file.
Empty file.
0