mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-01 23:50:11 +00:00
commit
f98c32f3fe
@ -8,6 +8,7 @@ OBJECTS = \
|
||||
git2.o \
|
||||
ls-remote.o \
|
||||
fetch.o \
|
||||
clone.o \
|
||||
index-pack.o
|
||||
|
||||
all: $(OBJECTS)
|
||||
|
68
examples/network/clone.c
Normal file
68
examples/network/clone.c
Normal file
@ -0,0 +1,68 @@
|
||||
#include "common.h"
|
||||
#include <git2.h>
|
||||
#include <git2/clone.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
struct dl_data {
|
||||
git_indexer_stats fetch_stats;
|
||||
git_indexer_stats checkout_stats;
|
||||
git_checkout_opts opts;
|
||||
int ret;
|
||||
int finished;
|
||||
const char *url;
|
||||
const char *path;
|
||||
};
|
||||
|
||||
static void *clone_thread(void *ptr)
|
||||
{
|
||||
struct dl_data *data = (struct dl_data *)ptr;
|
||||
git_repository *repo = NULL;
|
||||
|
||||
// Kick off the clone
|
||||
data->ret = git_clone(&repo, data->url, data->path,
|
||||
&data->fetch_stats, &data->checkout_stats,
|
||||
&data->opts);
|
||||
if (repo) git_repository_free(repo);
|
||||
data->finished = 1;
|
||||
|
||||
pthread_exit(&data->ret);
|
||||
}
|
||||
|
||||
int do_clone(git_repository *repo, int argc, char **argv)
|
||||
{
|
||||
struct dl_data data = {0};
|
||||
pthread_t worker;
|
||||
|
||||
// Validate args
|
||||
if (argc < 3) {
|
||||
printf("USAGE: %s <url> <path>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Data for background thread
|
||||
data.url = argv[1];
|
||||
data.path = argv[2];
|
||||
data.opts.disable_filters = 1;
|
||||
printf("Cloning '%s' to '%s'\n", data.url, data.path);
|
||||
|
||||
// Create the worker thread
|
||||
pthread_create(&worker, NULL, clone_thread, &data);
|
||||
|
||||
// Watch for progress information
|
||||
do {
|
||||
usleep(10000);
|
||||
printf("Fetch %d/%d – Checkout %d/%d\n",
|
||||
data.fetch_stats.processed, data.fetch_stats.total,
|
||||
data.checkout_stats.processed, data.checkout_stats.total);
|
||||
} while (!data.finished);
|
||||
printf("Fetch %d/%d – Checkout %d/%d\n",
|
||||
data.fetch_stats.processed, data.fetch_stats.total,
|
||||
data.checkout_stats.processed, data.checkout_stats.total);
|
||||
|
||||
return data.ret;
|
||||
}
|
||||
|
@ -10,5 +10,6 @@ int parse_pkt_line(git_repository *repo, int argc, char **argv);
|
||||
int show_remote(git_repository *repo, int argc, char **argv);
|
||||
int fetch(git_repository *repo, int argc, char **argv);
|
||||
int index_pack(git_repository *repo, int argc, char **argv);
|
||||
int do_clone(git_repository *repo, int argc, char **argv);
|
||||
|
||||
#endif /* __COMMON_H__ */
|
||||
|
@ -13,6 +13,7 @@ struct {
|
||||
} commands[] = {
|
||||
{"ls-remote", ls_remote},
|
||||
{"fetch", fetch},
|
||||
{"clone", do_clone},
|
||||
{"index-pack", index_pack},
|
||||
{ NULL, NULL}
|
||||
};
|
||||
|
@ -37,6 +37,8 @@
|
||||
#include "git2/index.h"
|
||||
#include "git2/config.h"
|
||||
#include "git2/remote.h"
|
||||
#include "git2/clone.h"
|
||||
#include "git2/checkout.h"
|
||||
|
||||
#include "git2/attr.h"
|
||||
#include "git2/branch.h"
|
||||
|
66
include/git2/checkout.h
Normal file
66
include/git2/checkout.h
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2012 the libgit2 contributors
|
||||
*
|
||||
* 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_checkout_h__
|
||||
#define INCLUDE_git_checkout_h__
|
||||
|
||||
#include "common.h"
|
||||
#include "types.h"
|
||||
#include "indexer.h"
|
||||
|
||||
|
||||
/**
|
||||
* @file git2/checkout.h
|
||||
* @brief Git checkout routines
|
||||
* @defgroup git_checkout Git checkout routines
|
||||
* @ingroup Git
|
||||
* @{
|
||||
*/
|
||||
GIT_BEGIN_DECL
|
||||
|
||||
|
||||
#define GIT_CHECKOUT_OVERWRITE_EXISTING 0 /* default */
|
||||
#define GIT_CHECKOUT_SKIP_EXISTING 1
|
||||
|
||||
/* Use zeros to indicate default settings */
|
||||
typedef struct git_checkout_opts {
|
||||
int existing_file_action; /* default: GIT_CHECKOUT_OVERWRITE_EXISTING */
|
||||
int disable_filters;
|
||||
int dir_mode; /* default is 0755 */
|
||||
int file_mode; /* default is 0644 */
|
||||
int file_open_flags; /* default is O_CREAT | O_TRUNC | O_WRONLY */
|
||||
} git_checkout_opts;
|
||||
|
||||
/**
|
||||
* Updates files in the working tree to match the commit pointed to by HEAD.
|
||||
*
|
||||
* @param repo repository to check out (must be non-bare)
|
||||
* @param opts specifies checkout options (may be NULL)
|
||||
* @param stats structure through which progress information is reported
|
||||
* @return 0 on success, GIT_ERROR otherwise (use giterr_last for information about the error)
|
||||
*/
|
||||
GIT_EXTERN(int) git_checkout_head(git_repository *repo,
|
||||
git_checkout_opts *opts,
|
||||
git_indexer_stats *stats);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Updates files in the working tree to match a commit pointed to by a ref.
|
||||
*
|
||||
* @param ref reference to follow to a commit
|
||||
* @param opts specifies checkout options (may be NULL)
|
||||
* @param stats structure through which progress information is reported
|
||||
* @return 0 on success, GIT_ERROR otherwise (use giterr_last for information about the error)
|
||||
*/
|
||||
GIT_EXTERN(int) git_checkout_reference(git_reference *ref,
|
||||
git_checkout_opts *opts,
|
||||
git_indexer_stats *stats);
|
||||
|
||||
|
||||
/** @} */
|
||||
GIT_END_DECL
|
||||
#endif
|
59
include/git2/clone.h
Normal file
59
include/git2/clone.h
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2012 the libgit2 contributors
|
||||
*
|
||||
* 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_clone_h__
|
||||
#define INCLUDE_git_clone_h__
|
||||
|
||||
#include "common.h"
|
||||
#include "types.h"
|
||||
#include "indexer.h"
|
||||
#include "checkout.h"
|
||||
|
||||
|
||||
/**
|
||||
* @file git2/clone.h
|
||||
* @brief Git cloning routines
|
||||
* @defgroup git_clone Git cloning routines
|
||||
* @ingroup Git
|
||||
* @{
|
||||
*/
|
||||
GIT_BEGIN_DECL
|
||||
|
||||
/**
|
||||
* Clone a remote repository, and checkout the branch pointed to by the remote
|
||||
* HEAD.
|
||||
*
|
||||
* @param out pointer that will receive the resulting repository object
|
||||
* @param origin_url repository to clone from
|
||||
* @param workdir_path local directory to clone to
|
||||
* @param fetch_stats pointer to structure that receives fetch progress information (may be NULL)
|
||||
* @param checkout_opts options for the checkout step (may be NULL)
|
||||
* @return 0 on success, GIT_ERROR otherwise (use giterr_last for information about the error)
|
||||
*/
|
||||
GIT_EXTERN(int) git_clone(git_repository **out,
|
||||
const char *origin_url,
|
||||
const char *workdir_path,
|
||||
git_indexer_stats *fetch_stats,
|
||||
git_indexer_stats *checkout_stats,
|
||||
git_checkout_opts *checkout_opts);
|
||||
|
||||
/**
|
||||
* Create a bare clone of a remote repository.
|
||||
*
|
||||
* @param out pointer that will receive the resulting repository object
|
||||
* @param origin_url repository to clone from
|
||||
* @param dest_path local directory to clone to
|
||||
* @param fetch_stats pointer to structure that receives fetch progress information (may be NULL)
|
||||
* @return 0 on success, GIT_ERROR otherwise (use giterr_last for information about the error)
|
||||
*/
|
||||
GIT_EXTERN(int) git_clone_bare(git_repository **out,
|
||||
const char *origin_url,
|
||||
const char *dest_path,
|
||||
git_indexer_stats *fetch_stats);
|
||||
|
||||
/** @} */
|
||||
GIT_END_DECL
|
||||
#endif
|
@ -8,6 +8,7 @@
|
||||
#define INCLUDE_git_index_h__
|
||||
|
||||
#include "common.h"
|
||||
#include "indexer.h"
|
||||
#include "types.h"
|
||||
#include "oid.h"
|
||||
|
||||
@ -335,15 +336,17 @@ GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_byindex(git_
|
||||
GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry);
|
||||
|
||||
/**
|
||||
* Read a tree into the index file
|
||||
* Read a tree into the index file with stats
|
||||
*
|
||||
* The current index contents will be replaced by the specified tree.
|
||||
* The current index contents will be replaced by the specified tree. The total
|
||||
* node count is collected in stats.
|
||||
*
|
||||
* @param index an existing index object
|
||||
* @param tree tree to read
|
||||
* @param stats structure that receives the total node count (may be NULL)
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree);
|
||||
GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree, git_indexer_stats *stats);
|
||||
|
||||
/** @} */
|
||||
GIT_END_DECL
|
||||
|
230
src/checkout.c
Normal file
230
src/checkout.c
Normal file
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2012 the libgit2 contributors
|
||||
*
|
||||
* 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 <assert.h>
|
||||
|
||||
#include "git2/checkout.h"
|
||||
#include "git2/repository.h"
|
||||
#include "git2/refs.h"
|
||||
#include "git2/tree.h"
|
||||
#include "git2/commit.h"
|
||||
#include "git2/blob.h"
|
||||
#include "git2/config.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "refs.h"
|
||||
#include "buffer.h"
|
||||
#include "repository.h"
|
||||
#include "filter.h"
|
||||
#include "blob.h"
|
||||
|
||||
GIT_BEGIN_DECL
|
||||
|
||||
|
||||
typedef struct tree_walk_data
|
||||
{
|
||||
git_indexer_stats *stats;
|
||||
git_checkout_opts *opts;
|
||||
git_repository *repo;
|
||||
git_odb *odb;
|
||||
bool do_symlinks;
|
||||
} tree_walk_data;
|
||||
|
||||
|
||||
static int blob_contents_to_link(tree_walk_data *data, git_buf *fnbuf,
|
||||
const git_oid *id)
|
||||
{
|
||||
int retcode = GIT_ERROR;
|
||||
git_blob *blob;
|
||||
|
||||
/* Get the link target */
|
||||
if (!(retcode = git_blob_lookup(&blob, data->repo, id))) {
|
||||
git_buf linktarget = GIT_BUF_INIT;
|
||||
if (!(retcode = git_blob__getbuf(&linktarget, blob))) {
|
||||
/* Create the link */
|
||||
const char *new = git_buf_cstr(&linktarget),
|
||||
*old = git_buf_cstr(fnbuf);
|
||||
retcode = data->do_symlinks
|
||||
? p_symlink(new, old)
|
||||
: git_futils_fake_symlink(new, old);
|
||||
}
|
||||
git_buf_free(&linktarget);
|
||||
git_blob_free(blob);
|
||||
}
|
||||
|
||||
return retcode;
|
||||
}
|
||||
|
||||
|
||||
static int blob_contents_to_file(git_repository *repo, git_buf *fnbuf,
|
||||
const git_tree_entry *entry, tree_walk_data *data)
|
||||
{
|
||||
int retcode = GIT_ERROR;
|
||||
int fd = -1;
|
||||
git_buf contents = GIT_BUF_INIT;
|
||||
const git_oid *id = git_tree_entry_id(entry);
|
||||
int file_mode = data->opts->file_mode;
|
||||
|
||||
/* Deal with pre-existing files */
|
||||
if (git_path_exists(git_buf_cstr(fnbuf)) &&
|
||||
data->opts->existing_file_action == GIT_CHECKOUT_SKIP_EXISTING)
|
||||
return 0;
|
||||
|
||||
/* Allow disabling of filters */
|
||||
if (data->opts->disable_filters) {
|
||||
git_blob *blob;
|
||||
if (!(retcode = git_blob_lookup(&blob, repo, id))) {
|
||||
retcode = git_blob__getbuf(&contents, blob);
|
||||
git_blob_free(blob);
|
||||
}
|
||||
} else {
|
||||
retcode = git_filter_blob_contents(&contents, repo, id, git_buf_cstr(fnbuf));
|
||||
}
|
||||
if (retcode < 0) goto bctf_cleanup;
|
||||
|
||||
/* Allow overriding of file mode */
|
||||
if (!file_mode)
|
||||
file_mode = git_tree_entry_attributes(entry);
|
||||
|
||||
if ((retcode = git_futils_mkpath2file(git_buf_cstr(fnbuf), data->opts->dir_mode)) < 0)
|
||||
goto bctf_cleanup;
|
||||
|
||||
fd = p_open(git_buf_cstr(fnbuf), data->opts->file_open_flags, file_mode);
|
||||
if (fd < 0) goto bctf_cleanup;
|
||||
|
||||
if (!p_write(fd, git_buf_cstr(&contents), git_buf_len(&contents)))
|
||||
retcode = 0;
|
||||
else
|
||||
retcode = GIT_ERROR;
|
||||
p_close(fd);
|
||||
|
||||
bctf_cleanup:
|
||||
git_buf_free(&contents);
|
||||
return retcode;
|
||||
}
|
||||
|
||||
static int checkout_walker(const char *path, const git_tree_entry *entry, void *payload)
|
||||
{
|
||||
int retcode = 0;
|
||||
tree_walk_data *data = (tree_walk_data*)payload;
|
||||
int attr = git_tree_entry_attributes(entry);
|
||||
git_buf fnbuf = GIT_BUF_INIT;
|
||||
git_buf_join_n(&fnbuf, '/', 3,
|
||||
git_repository_workdir(data->repo),
|
||||
path,
|
||||
git_tree_entry_name(entry));
|
||||
|
||||
switch(git_tree_entry_type(entry))
|
||||
{
|
||||
case GIT_OBJ_TREE:
|
||||
/* Nothing to do; the blob handling creates necessary directories. */
|
||||
break;
|
||||
|
||||
case GIT_OBJ_COMMIT:
|
||||
/* Submodule */
|
||||
git_futils_mkpath2file(git_buf_cstr(&fnbuf), data->opts->dir_mode);
|
||||
retcode = p_mkdir(git_buf_cstr(&fnbuf), data->opts->dir_mode);
|
||||
break;
|
||||
|
||||
case GIT_OBJ_BLOB:
|
||||
if (S_ISLNK(attr)) {
|
||||
retcode = blob_contents_to_link(data, &fnbuf,
|
||||
git_tree_entry_id(entry));
|
||||
} else {
|
||||
retcode = blob_contents_to_file(data->repo, &fnbuf, entry, data);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
retcode = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
git_buf_free(&fnbuf);
|
||||
data->stats->processed++;
|
||||
return retcode;
|
||||
}
|
||||
|
||||
|
||||
int git_checkout_head(git_repository *repo, git_checkout_opts *opts, git_indexer_stats *stats)
|
||||
{
|
||||
int retcode = GIT_ERROR;
|
||||
git_indexer_stats dummy_stats;
|
||||
git_checkout_opts default_opts = {0};
|
||||
git_tree *tree;
|
||||
tree_walk_data payload;
|
||||
git_config *cfg;
|
||||
|
||||
assert(repo);
|
||||
if (!opts) opts = &default_opts;
|
||||
if (!stats) stats = &dummy_stats;
|
||||
|
||||
/* Default options */
|
||||
if (!opts->existing_file_action)
|
||||
opts->existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING;
|
||||
/* opts->disable_filters is false by default */
|
||||
if (!opts->dir_mode) opts->dir_mode = GIT_DIR_MODE;
|
||||
if (!opts->file_open_flags)
|
||||
opts->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
|
||||
|
||||
if (git_repository_is_bare(repo)) {
|
||||
giterr_set(GITERR_INVALID, "Checkout is not allowed for bare repositories");
|
||||
return GIT_ERROR;
|
||||
}
|
||||
|
||||
/* Determine if symlinks should be handled */
|
||||
if (!git_repository_config(&cfg, repo)) {
|
||||
int temp = true;
|
||||
if (!git_config_get_bool(&temp, cfg, "core.symlinks")) {
|
||||
payload.do_symlinks = !!temp;
|
||||
}
|
||||
git_config_free(cfg);
|
||||
}
|
||||
|
||||
stats->total = stats->processed = 0;
|
||||
payload.stats = stats;
|
||||
payload.opts = opts;
|
||||
payload.repo = repo;
|
||||
if (git_repository_odb(&payload.odb, repo) < 0) return GIT_ERROR;
|
||||
|
||||
if (!git_repository_head_tree(&tree, repo)) {
|
||||
git_index *idx;
|
||||
if (!(retcode = git_repository_index(&idx, repo))) {
|
||||
if (!(retcode = git_index_read_tree(idx, tree, stats))) {
|
||||
git_index_write(idx);
|
||||
retcode = git_tree_walk(tree, checkout_walker, GIT_TREEWALK_POST, &payload);
|
||||
}
|
||||
git_index_free(idx);
|
||||
}
|
||||
git_tree_free(tree);
|
||||
}
|
||||
|
||||
git_odb_free(payload.odb);
|
||||
return retcode;
|
||||
}
|
||||
|
||||
|
||||
int git_checkout_reference(git_reference *ref,
|
||||
git_checkout_opts *opts,
|
||||
git_indexer_stats *stats)
|
||||
{
|
||||
git_repository *repo= git_reference_owner(ref);
|
||||
git_reference *head = NULL;
|
||||
int retcode = GIT_ERROR;
|
||||
|
||||
if ((retcode = git_reference_create_symbolic(&head, repo, GIT_HEAD_FILE,
|
||||
git_reference_name(ref), true)) < 0)
|
||||
return retcode;
|
||||
|
||||
retcode = git_checkout_head(git_reference_owner(ref), opts, stats);
|
||||
|
||||
git_reference_free(head);
|
||||
return retcode;
|
||||
}
|
||||
|
||||
|
||||
GIT_END_DECL
|
254
src/clone.c
Normal file
254
src/clone.c
Normal file
@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2012 the libgit2 contributors
|
||||
*
|
||||
* 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 <assert.h>
|
||||
|
||||
#ifndef GIT_WIN32
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
#include "git2/clone.h"
|
||||
#include "git2/remote.h"
|
||||
#include "git2/revparse.h"
|
||||
#include "git2/branch.h"
|
||||
#include "git2/config.h"
|
||||
#include "git2/checkout.h"
|
||||
#include "git2/commit.h"
|
||||
#include "git2/tree.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "remote.h"
|
||||
#include "fileops.h"
|
||||
#include "refs.h"
|
||||
#include "path.h"
|
||||
|
||||
GIT_BEGIN_DECL
|
||||
|
||||
struct HeadInfo {
|
||||
git_repository *repo;
|
||||
git_oid remote_head_oid;
|
||||
git_buf branchname;
|
||||
};
|
||||
|
||||
static int create_tracking_branch(git_repository *repo, const git_oid *target, const char *name)
|
||||
{
|
||||
git_object *head_obj = NULL;
|
||||
git_reference *branch_ref;
|
||||
int retcode = GIT_ERROR;
|
||||
|
||||
/* Find the target commit */
|
||||
if (git_object_lookup(&head_obj, repo, target, GIT_OBJ_ANY) < 0)
|
||||
return GIT_ERROR;
|
||||
|
||||
/* Create the new branch */
|
||||
if (!git_branch_create(&branch_ref, repo, name, head_obj, 0)) {
|
||||
git_config *cfg;
|
||||
|
||||
git_reference_free(branch_ref);
|
||||
/* Set up tracking */
|
||||
if (!git_repository_config(&cfg, repo)) {
|
||||
git_buf remote = GIT_BUF_INIT;
|
||||
git_buf merge = GIT_BUF_INIT;
|
||||
git_buf merge_target = GIT_BUF_INIT;
|
||||
if (!git_buf_printf(&remote, "branch.%s.remote", name) &&
|
||||
!git_buf_printf(&merge, "branch.%s.merge", name) &&
|
||||
!git_buf_printf(&merge_target, "refs/heads/%s", name) &&
|
||||
!git_config_set_string(cfg, git_buf_cstr(&remote), "origin") &&
|
||||
!git_config_set_string(cfg, git_buf_cstr(&merge), git_buf_cstr(&merge_target))) {
|
||||
retcode = 0;
|
||||
}
|
||||
git_buf_free(&remote);
|
||||
git_buf_free(&merge);
|
||||
git_buf_free(&merge_target);
|
||||
git_config_free(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
git_object_free(head_obj);
|
||||
return retcode;
|
||||
}
|
||||
|
||||
static int reference_matches_remote_head(const char *head_name, void *payload)
|
||||
{
|
||||
struct HeadInfo *head_info = (struct HeadInfo *)payload;
|
||||
git_oid oid;
|
||||
|
||||
/* Stop looking if we've already found a match */
|
||||
if (git_buf_len(&head_info->branchname) > 0) return 0;
|
||||
|
||||
if (!git_reference_name_to_oid(&oid, head_info->repo, head_name) &&
|
||||
!git_oid_cmp(&head_info->remote_head_oid, &oid)) {
|
||||
git_buf_puts(&head_info->branchname,
|
||||
head_name+strlen("refs/remotes/origin/"));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int update_head_to_new_branch(git_repository *repo, const git_oid *target, const char *name)
|
||||
{
|
||||
int retcode = GIT_ERROR;
|
||||
|
||||
if (!create_tracking_branch(repo, target, name)) {
|
||||
git_reference *head;
|
||||
if (!git_reference_lookup(&head, repo, GIT_HEAD_FILE)) {
|
||||
git_buf targetbuf = GIT_BUF_INIT;
|
||||
if (!git_buf_printf(&targetbuf, "refs/heads/%s", name)) {
|
||||
retcode = git_reference_set_target(head, git_buf_cstr(&targetbuf));
|
||||
}
|
||||
git_buf_free(&targetbuf);
|
||||
git_reference_free(head);
|
||||
}
|
||||
}
|
||||
|
||||
return retcode;
|
||||
}
|
||||
|
||||
static int update_head_to_remote(git_repository *repo, git_remote *remote)
|
||||
{
|
||||
int retcode = GIT_ERROR;
|
||||
git_remote_head *remote_head;
|
||||
git_oid oid;
|
||||
struct HeadInfo head_info;
|
||||
|
||||
/* Get the remote's HEAD. This is always the first ref in remote->refs. */
|
||||
remote_head = remote->refs.contents[0];
|
||||
git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid);
|
||||
git_buf_init(&head_info.branchname, 16);
|
||||
head_info.repo = repo;
|
||||
|
||||
/* Check to see if "master" matches the remote head */
|
||||
if (!git_reference_name_to_oid(&oid, repo, "refs/remotes/origin/master") &&
|
||||
!git_oid_cmp(&remote_head->oid, &oid)) {
|
||||
retcode = update_head_to_new_branch(repo, &oid, "master");
|
||||
}
|
||||
/* Not master. Check all the other refs. */
|
||||
else if (!git_reference_foreach(repo, GIT_REF_LISTALL,
|
||||
reference_matches_remote_head,
|
||||
&head_info) &&
|
||||
git_buf_len(&head_info.branchname) > 0) {
|
||||
retcode = update_head_to_new_branch(repo, &head_info.remote_head_oid,
|
||||
git_buf_cstr(&head_info.branchname));
|
||||
}
|
||||
|
||||
git_buf_free(&head_info.branchname);
|
||||
return retcode;
|
||||
}
|
||||
|
||||
/*
|
||||
* submodules?
|
||||
*/
|
||||
|
||||
|
||||
|
||||
static int setup_remotes_and_fetch(git_repository *repo,
|
||||
const char *origin_url,
|
||||
git_indexer_stats *fetch_stats)
|
||||
{
|
||||
int retcode = GIT_ERROR;
|
||||
git_remote *origin = NULL;
|
||||
git_off_t bytes = 0;
|
||||
git_indexer_stats dummy_stats;
|
||||
|
||||
if (!fetch_stats) fetch_stats = &dummy_stats;
|
||||
|
||||
/* Create the "origin" remote */
|
||||
if (!git_remote_add(&origin, repo, "origin", origin_url)) {
|
||||
/* Connect and download everything */
|
||||
if (!git_remote_connect(origin, GIT_DIR_FETCH)) {
|
||||
if (!git_remote_download(origin, &bytes, fetch_stats)) {
|
||||
/* Create "origin/foo" branches for all remote branches */
|
||||
if (!git_remote_update_tips(origin)) {
|
||||
/* Point HEAD to the same ref as the remote's head */
|
||||
if (!update_head_to_remote(repo, origin)) {
|
||||
retcode = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
git_remote_disconnect(origin);
|
||||
}
|
||||
git_remote_free(origin);
|
||||
}
|
||||
|
||||
return retcode;
|
||||
}
|
||||
|
||||
|
||||
static bool path_is_okay(const char *path)
|
||||
{
|
||||
/* The path must either not exist, or be an empty directory */
|
||||
if (!git_path_exists(path)) return true;
|
||||
if (!git_path_is_empty_dir(path)) {
|
||||
giterr_set(GITERR_INVALID,
|
||||
"'%s' exists and is not an empty directory", path);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static int clone_internal(git_repository **out,
|
||||
const char *origin_url,
|
||||
const char *path,
|
||||
git_indexer_stats *fetch_stats,
|
||||
int is_bare)
|
||||
{
|
||||
int retcode = GIT_ERROR;
|
||||
git_repository *repo = NULL;
|
||||
git_indexer_stats dummy_stats;
|
||||
|
||||
if (!fetch_stats) fetch_stats = &dummy_stats;
|
||||
|
||||
if (!path_is_okay(path)) {
|
||||
return GIT_ERROR;
|
||||
}
|
||||
|
||||
if (!(retcode = git_repository_init(&repo, path, is_bare))) {
|
||||
if ((retcode = setup_remotes_and_fetch(repo, origin_url, fetch_stats)) < 0) {
|
||||
/* Failed to fetch; clean up */
|
||||
git_repository_free(repo);
|
||||
git_futils_rmdir_r(path, GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
} else {
|
||||
*out = repo;
|
||||
retcode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return retcode;
|
||||
}
|
||||
|
||||
int git_clone_bare(git_repository **out,
|
||||
const char *origin_url,
|
||||
const char *dest_path,
|
||||
git_indexer_stats *fetch_stats)
|
||||
{
|
||||
assert(out && origin_url && dest_path);
|
||||
return clone_internal(out, origin_url, dest_path, fetch_stats, 1);
|
||||
}
|
||||
|
||||
|
||||
int git_clone(git_repository **out,
|
||||
const char *origin_url,
|
||||
const char *workdir_path,
|
||||
git_indexer_stats *fetch_stats,
|
||||
git_indexer_stats *checkout_stats,
|
||||
git_checkout_opts *checkout_opts)
|
||||
{
|
||||
int retcode = GIT_ERROR;
|
||||
|
||||
assert(out && origin_url && workdir_path);
|
||||
|
||||
if (!(retcode = clone_internal(out, origin_url, workdir_path, fetch_stats, 0))) {
|
||||
retcode = git_checkout_head(*out, checkout_opts, checkout_stats);
|
||||
}
|
||||
|
||||
return retcode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
GIT_END_DECL
|
96
src/crlf.c
96
src/crlf.c
@ -184,7 +184,87 @@ static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *sou
|
||||
return drop_crlf(dest, source);
|
||||
}
|
||||
|
||||
int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path)
|
||||
static int convert_line_endings(git_buf *dest, const git_buf *source, const char *ending)
|
||||
{
|
||||
const char *scan = git_buf_cstr(source),
|
||||
*next,
|
||||
*scan_end = git_buf_cstr(source) + git_buf_len(source);
|
||||
|
||||
while ((next = memchr(scan, '\n', scan_end - scan)) != NULL) {
|
||||
if (next > scan)
|
||||
git_buf_put(dest, scan, next-scan);
|
||||
git_buf_puts(dest, ending);
|
||||
scan = next + 1;
|
||||
}
|
||||
|
||||
git_buf_put(dest, scan, scan_end - scan);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *line_ending(struct crlf_filter *filter)
|
||||
{
|
||||
switch (filter->attrs.crlf_action) {
|
||||
case GIT_CRLF_BINARY:
|
||||
case GIT_CRLF_INPUT:
|
||||
return "\n";
|
||||
|
||||
case GIT_CRLF_CRLF:
|
||||
return "\r\n";
|
||||
|
||||
case GIT_CRLF_AUTO:
|
||||
case GIT_CRLF_TEXT:
|
||||
case GIT_CRLF_GUESS:
|
||||
break;
|
||||
|
||||
default:
|
||||
goto line_ending_error;
|
||||
}
|
||||
|
||||
switch (filter->attrs.eol) {
|
||||
case GIT_EOL_UNSET:
|
||||
return GIT_EOL_NATIVE == GIT_EOL_CRLF
|
||||
? "\r\n"
|
||||
: "\n";
|
||||
|
||||
case GIT_EOL_CRLF:
|
||||
return "\r\n";
|
||||
|
||||
case GIT_EOL_LF:
|
||||
return "\n";
|
||||
|
||||
default:
|
||||
goto line_ending_error;
|
||||
}
|
||||
|
||||
line_ending_error:
|
||||
giterr_set(GITERR_INVALID, "Invalid input to line ending filter");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int crlf_apply_to_workdir(git_filter *self, git_buf *dest, const git_buf *source)
|
||||
{
|
||||
struct crlf_filter *filter = (struct crlf_filter *)self;
|
||||
const char *workdir_ending = NULL;
|
||||
|
||||
assert (self && dest && source);
|
||||
|
||||
/* Empty file? Nothing to do. */
|
||||
if (git_buf_len(source) == 0)
|
||||
return 0;
|
||||
|
||||
/* Determine proper line ending */
|
||||
workdir_ending = line_ending(filter);
|
||||
if (!workdir_ending) return -1;
|
||||
|
||||
/* If the line ending is '\n', just copy the input */
|
||||
if (!strcmp(workdir_ending, "\n"))
|
||||
return git_buf_puts(dest, git_buf_cstr(source));
|
||||
|
||||
return convert_line_endings(dest, source, workdir_ending);
|
||||
}
|
||||
|
||||
static int find_and_add_filter(git_vector *filters, git_repository *repo, const char *path,
|
||||
int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source))
|
||||
{
|
||||
struct crlf_attrs ca;
|
||||
struct crlf_filter *filter;
|
||||
@ -206,8 +286,7 @@ int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const
|
||||
if (ca.crlf_action == GIT_CRLF_GUESS) {
|
||||
int auto_crlf;
|
||||
|
||||
if ((error = git_repository__cvar(
|
||||
&auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0)
|
||||
if ((error = git_repository__cvar(&auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0)
|
||||
return error;
|
||||
|
||||
if (auto_crlf == GIT_AUTO_CRLF_FALSE)
|
||||
@ -219,10 +298,19 @@ int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const
|
||||
filter = git__malloc(sizeof(struct crlf_filter));
|
||||
GITERR_CHECK_ALLOC(filter);
|
||||
|
||||
filter->f.apply = &crlf_apply_to_odb;
|
||||
filter->f.apply = apply;
|
||||
filter->f.do_free = NULL;
|
||||
memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs));
|
||||
|
||||
return git_vector_insert(filters, filter);
|
||||
}
|
||||
|
||||
int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path)
|
||||
{
|
||||
return find_and_add_filter(filters, repo, path, &crlf_apply_to_odb);
|
||||
}
|
||||
|
||||
int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path)
|
||||
{
|
||||
return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir);
|
||||
}
|
||||
|
@ -484,3 +484,14 @@ int git_futils_find_global_file(git_buf *path, const char *filename)
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int git_futils_fake_symlink(const char *old, const char *new)
|
||||
{
|
||||
int retcode = GIT_ERROR;
|
||||
int fd = git_futils_creat_withpath(new, 0755, 0644);
|
||||
if (fd >= 0) {
|
||||
retcode = p_write(fd, old, strlen(old));
|
||||
p_close(fd);
|
||||
}
|
||||
return retcode;
|
||||
}
|
||||
|
@ -179,4 +179,14 @@ extern int git_futils_find_global_file(git_buf *path, const char *filename);
|
||||
*/
|
||||
extern int git_futils_find_system_file(git_buf *path, const char *filename);
|
||||
|
||||
|
||||
/**
|
||||
* Create a "fake" symlink (text file containing the target path).
|
||||
*
|
||||
* @param new symlink file to be created
|
||||
* @param old original symlink target
|
||||
* @return 0 on success, -1 on error
|
||||
*/
|
||||
extern int git_futils_fake_symlink(const char *new, const char *old);
|
||||
|
||||
#endif /* INCLUDE_fileops_h__ */
|
||||
|
40
src/filter.c
40
src/filter.c
@ -11,6 +11,7 @@
|
||||
#include "filter.h"
|
||||
#include "repository.h"
|
||||
#include "git2/config.h"
|
||||
#include "blob.h"
|
||||
|
||||
/* Tweaked from Core Git. I wonder what we could use this for... */
|
||||
void git_text_gather_stats(git_text_stats *stats, const git_buf *text)
|
||||
@ -95,8 +96,9 @@ int git_filters_load(git_vector *filters, git_repository *repo, const char *path
|
||||
if (error < 0)
|
||||
return error;
|
||||
} else {
|
||||
giterr_set(GITERR_INVALID, "Worktree filters are not implemented yet");
|
||||
return -1;
|
||||
error = git_filter_add__crlf_to_workdir(filters, repo, path);
|
||||
if (error < 0)
|
||||
return error;
|
||||
}
|
||||
|
||||
return (int)filters->length;
|
||||
@ -163,3 +165,37 @@ int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unfiltered_blob_contents(git_buf *out, git_repository *repo, const git_oid *blob_id)
|
||||
{
|
||||
int retcode = GIT_ERROR;
|
||||
git_blob *blob;
|
||||
|
||||
if (!(retcode = git_blob_lookup(&blob, repo, blob_id)))
|
||||
{
|
||||
retcode = git_blob__getbuf(out, blob);
|
||||
git_blob_free(blob);
|
||||
}
|
||||
|
||||
return retcode;
|
||||
}
|
||||
|
||||
int git_filter_blob_contents(git_buf *out, git_repository *repo, const git_oid *oid, const char *path)
|
||||
{
|
||||
int retcode = GIT_ERROR;
|
||||
|
||||
git_buf unfiltered = GIT_BUF_INIT;
|
||||
if (!unfiltered_blob_contents(&unfiltered, repo, oid)) {
|
||||
git_vector filters = GIT_VECTOR_INIT;
|
||||
if (git_filters_load(&filters,
|
||||
repo, path, GIT_FILTER_TO_WORKTREE) >= 0) {
|
||||
git_buf_clear(out);
|
||||
retcode = git_filters_apply(out, &unfiltered, &filters);
|
||||
}
|
||||
|
||||
git_filters_free(&filters);
|
||||
}
|
||||
|
||||
git_buf_free(&unfiltered);
|
||||
return retcode;
|
||||
}
|
||||
|
||||
|
15
src/filter.h
15
src/filter.h
@ -96,6 +96,9 @@ extern void git_filters_free(git_vector *filters);
|
||||
/* Strip CRLF, from Worktree to ODB */
|
||||
extern int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path);
|
||||
|
||||
/* Add CRLF, from ODB to worktree */
|
||||
extern int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path);
|
||||
|
||||
|
||||
/*
|
||||
* PLAINTEXT API
|
||||
@ -116,4 +119,16 @@ extern void git_text_gather_stats(git_text_stats *stats, const git_buf *text);
|
||||
*/
|
||||
extern int git_text_is_binary(git_text_stats *stats);
|
||||
|
||||
|
||||
/**
|
||||
* Get the content of a blob after all filters have been run.
|
||||
*
|
||||
* @param out buffer to receive the contents
|
||||
* @param repo repository containing the blob
|
||||
* @param oid object id for the blob
|
||||
* @param path path to the blob's output file, relative to the workdir root
|
||||
* @return 0 on success, an error code otherwise
|
||||
*/
|
||||
extern int git_filter_blob_contents(git_buf *out, git_repository *repo, const git_oid *oid, const char *path);
|
||||
|
||||
#endif
|
||||
|
22
src/index.c
22
src/index.c
@ -986,12 +986,19 @@ int git_index_entry_stage(const git_index_entry *entry)
|
||||
return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT;
|
||||
}
|
||||
|
||||
typedef struct read_tree_data {
|
||||
git_index *index;
|
||||
git_indexer_stats *stats;
|
||||
} read_tree_data;
|
||||
|
||||
static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *data)
|
||||
{
|
||||
git_index *index = data;
|
||||
read_tree_data *rtd = data;
|
||||
git_index_entry *entry = NULL;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
rtd->stats->total++;
|
||||
|
||||
if (git_tree_entry__is_tree(tentry))
|
||||
return 0;
|
||||
|
||||
@ -1006,7 +1013,7 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da
|
||||
entry->path = git_buf_detach(&path);
|
||||
git_buf_free(&path);
|
||||
|
||||
if (index_insert(index, entry, 0) < 0) {
|
||||
if (index_insert(rtd->index, entry, 0) < 0) {
|
||||
index_entry_free(entry);
|
||||
return -1;
|
||||
}
|
||||
@ -1014,9 +1021,16 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_index_read_tree(git_index *index, git_tree *tree)
|
||||
int git_index_read_tree(git_index *index, git_tree *tree, git_indexer_stats *stats)
|
||||
{
|
||||
git_indexer_stats dummy_stats;
|
||||
read_tree_data rtd = {index, NULL};
|
||||
|
||||
if (!stats) stats = &dummy_stats;
|
||||
stats->total = 0;
|
||||
rtd.stats = stats;
|
||||
|
||||
git_index_clear(index);
|
||||
|
||||
return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, index);
|
||||
return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, &rtd);
|
||||
}
|
||||
|
75
src/path.c
75
src/path.c
@ -387,6 +387,69 @@ bool git_path_isfile(const char *path)
|
||||
return S_ISREG(st.st_mode) != 0;
|
||||
}
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
|
||||
bool git_path_is_empty_dir(const char *path)
|
||||
{
|
||||
git_buf pathbuf = GIT_BUF_INIT;
|
||||
HANDLE hFind = INVALID_HANDLE_VALUE;
|
||||
wchar_t *wbuf;
|
||||
WIN32_FIND_DATAW ffd;
|
||||
bool retval = true;
|
||||
|
||||
if (!git_path_isdir(path)) return false;
|
||||
|
||||
git_buf_printf(&pathbuf, "%s\\*", path);
|
||||
wbuf = gitwin_to_utf16(git_buf_cstr(&pathbuf));
|
||||
|
||||
hFind = FindFirstFileW(wbuf, &ffd);
|
||||
if (INVALID_HANDLE_VALUE == hFind) {
|
||||
giterr_set(GITERR_OS, "Couldn't open '%s'", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
do {
|
||||
if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) {
|
||||
retval = false;
|
||||
}
|
||||
} while (FindNextFileW(hFind, &ffd) != 0);
|
||||
|
||||
FindClose(hFind);
|
||||
git_buf_free(&pathbuf);
|
||||
git__free(wbuf);
|
||||
return retval;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool git_path_is_empty_dir(const char *path)
|
||||
{
|
||||
DIR *dir = NULL;
|
||||
struct dirent *e;
|
||||
bool retval = true;
|
||||
|
||||
if (!git_path_isdir(path)) return false;
|
||||
|
||||
dir = opendir(path);
|
||||
if (!dir) {
|
||||
giterr_set(GITERR_OS, "Couldn't open '%s'", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((e = readdir(dir)) != NULL) {
|
||||
if (!git_path_is_dot_or_dotdot(e->d_name)) {
|
||||
giterr_set(GITERR_INVALID,
|
||||
"'%s' exists and is not an empty directory", path);
|
||||
retval = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
return retval;
|
||||
}
|
||||
#endif
|
||||
|
||||
int git_path_lstat(const char *path, struct stat *st)
|
||||
{
|
||||
int err = 0;
|
||||
@ -551,14 +614,6 @@ int git_path_cmp(
|
||||
return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* Taken from git.git */
|
||||
GIT_INLINE(int) is_dot_or_dotdot(const char *name)
|
||||
{
|
||||
return (name[0] == '.' &&
|
||||
(name[1] == '\0' ||
|
||||
(name[1] == '.' && name[2] == '\0')));
|
||||
}
|
||||
|
||||
int git_path_direach(
|
||||
git_buf *path,
|
||||
int (*fn)(void *, git_buf *),
|
||||
@ -587,7 +642,7 @@ int git_path_direach(
|
||||
while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) {
|
||||
int result;
|
||||
|
||||
if (is_dot_or_dotdot(de->d_name))
|
||||
if (git_path_is_dot_or_dotdot(de->d_name))
|
||||
continue;
|
||||
|
||||
if (git_buf_puts(path, de->d_name) < 0) {
|
||||
@ -646,7 +701,7 @@ int git_path_dirload(
|
||||
char *entry_path;
|
||||
size_t entry_len;
|
||||
|
||||
if (is_dot_or_dotdot(de->d_name))
|
||||
if (git_path_is_dot_or_dotdot(de->d_name))
|
||||
continue;
|
||||
|
||||
entry_len = strlen(de->d_name);
|
||||
|
22
src/path.h
22
src/path.h
@ -80,7 +80,24 @@ extern int git_path_to_dir(git_buf *path);
|
||||
*/
|
||||
extern void git_path_string_to_dir(char* path, size_t size);
|
||||
|
||||
/**
|
||||
* Taken from git.git; returns nonzero if the given path is "." or "..".
|
||||
*/
|
||||
GIT_INLINE(int) git_path_is_dot_or_dotdot(const char *name)
|
||||
{
|
||||
return (name[0] == '.' &&
|
||||
(name[1] == '\0' ||
|
||||
(name[1] == '.' && name[2] == '\0')));
|
||||
}
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
GIT_INLINE(int) git_path_is_dot_or_dotdotW(const wchar_t *name)
|
||||
{
|
||||
return (name[0] == L'.' &&
|
||||
(name[1] == L'\0' ||
|
||||
(name[1] == L'.' && name[2] == L'\0')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert backslashes in path to forward slashes.
|
||||
*/
|
||||
@ -129,6 +146,11 @@ extern bool git_path_isdir(const char *path);
|
||||
*/
|
||||
extern bool git_path_isfile(const char *path);
|
||||
|
||||
/**
|
||||
* Check if the given path is a directory, and is empty.
|
||||
*/
|
||||
extern bool git_path_is_empty_dir(const char *path);
|
||||
|
||||
/**
|
||||
* Stat a file and/or link and set error if needed.
|
||||
*/
|
||||
|
@ -80,7 +80,7 @@ int git_reset(
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (git_index_read_tree(index, tree) < 0) {
|
||||
if (git_index_read_tree(index, tree, NULL) < 0) {
|
||||
giterr_set(GITERR_INDEX, "%s - Failed to update the index.", ERROR_MSG);
|
||||
goto cleanup;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
#define p_lstat(p,b) lstat(p,b)
|
||||
#define p_readlink(a, b, c) readlink(a, b, c)
|
||||
#define p_link(o,n) link(o, n)
|
||||
#define p_symlink(o,n) symlink(o,n)
|
||||
#define p_unlink(p) unlink(p)
|
||||
#define p_mkdir(p,m) mkdir(p, m)
|
||||
#define p_fsync(fd) fsync(fd)
|
||||
|
@ -33,6 +33,7 @@ GIT_INLINE(int) p_mkdir(const char *path, mode_t mode)
|
||||
extern int p_unlink(const char *path);
|
||||
extern int p_lstat(const char *file_name, struct stat *buf);
|
||||
extern int p_readlink(const char *link, char *target, size_t target_len);
|
||||
extern int p_symlink(const char *old, const char *new);
|
||||
extern int p_hide_directory__w32(const char *path);
|
||||
extern char *p_realpath(const char *orig_path, char *buffer);
|
||||
extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr);
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "../posix.h"
|
||||
#include "path.h"
|
||||
#include "utf-conv.h"
|
||||
#include "repository.h"
|
||||
#include <errno.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
@ -224,6 +225,14 @@ int p_readlink(const char *link, char *target, size_t target_len)
|
||||
return dwRet;
|
||||
}
|
||||
|
||||
int p_symlink(const char *old, const char *new)
|
||||
{
|
||||
/* Real symlinks on NTFS require admin privileges. Until this changes,
|
||||
* libgit2 just creates a text file with the link target in the contents.
|
||||
*/
|
||||
return git_futils_fake_symlink(old, new);
|
||||
}
|
||||
|
||||
int p_open(const char *path, int flags, ...)
|
||||
{
|
||||
int fd;
|
||||
|
186
tests-clar/checkout/checkout.c
Normal file
186
tests-clar/checkout/checkout.c
Normal file
@ -0,0 +1,186 @@
|
||||
#include "clar_libgit2.h"
|
||||
|
||||
#include "git2/checkout.h"
|
||||
#include "repository.h"
|
||||
|
||||
|
||||
static git_repository *g_repo;
|
||||
|
||||
void test_checkout_checkout__initialize(void)
|
||||
{
|
||||
const char *attributes = "* text eol=lf\n";
|
||||
|
||||
g_repo = cl_git_sandbox_init("testrepo");
|
||||
cl_git_mkfile("./testrepo/.gitattributes", attributes);
|
||||
}
|
||||
|
||||
void test_checkout_checkout__cleanup(void)
|
||||
{
|
||||
cl_git_sandbox_cleanup();
|
||||
}
|
||||
|
||||
|
||||
static void test_file_contents(const char *path, const char *expectedcontents)
|
||||
{
|
||||
int fd;
|
||||
char buffer[1024] = {0};
|
||||
|
||||
fd = p_open(path, O_RDONLY);
|
||||
cl_assert(fd >= 0);
|
||||
|
||||
cl_assert_equal_i(p_read(fd, buffer, 1024), strlen(expectedcontents));
|
||||
cl_assert_equal_s(expectedcontents, buffer);
|
||||
cl_git_pass(p_close(fd));
|
||||
}
|
||||
|
||||
|
||||
void test_checkout_checkout__bare(void)
|
||||
{
|
||||
cl_git_sandbox_cleanup();
|
||||
g_repo = cl_git_sandbox_init("testrepo.git");
|
||||
cl_git_fail(git_checkout_head(g_repo, NULL, NULL));
|
||||
}
|
||||
|
||||
void test_checkout_checkout__default(void)
|
||||
{
|
||||
cl_git_pass(git_checkout_head(g_repo, NULL, NULL));
|
||||
test_file_contents("./testrepo/README", "hey there\n");
|
||||
test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n");
|
||||
test_file_contents("./testrepo/new.txt", "my new file\n");
|
||||
}
|
||||
|
||||
|
||||
void test_checkout_checkout__crlf(void)
|
||||
{
|
||||
const char *attributes =
|
||||
"branch_file.txt text eol=crlf\n"
|
||||
"new.txt text eol=lf\n";
|
||||
const char *expected_readme_text =
|
||||
#ifdef GIT_WIN32
|
||||
"hey there\r\n";
|
||||
#else
|
||||
"hey there\n";
|
||||
#endif
|
||||
cl_git_mkfile("./testrepo/.gitattributes", attributes);
|
||||
cl_git_pass(git_checkout_head(g_repo, NULL, NULL));
|
||||
test_file_contents("./testrepo/README", expected_readme_text);
|
||||
test_file_contents("./testrepo/new.txt", "my new file\n");
|
||||
test_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n");
|
||||
}
|
||||
|
||||
static void enable_symlinks(bool enable)
|
||||
{
|
||||
git_config *cfg;
|
||||
cl_git_pass(git_repository_config(&cfg, g_repo));
|
||||
cl_git_pass(git_config_set_bool(cfg, "core.symlinks", enable));
|
||||
git_config_free(cfg);
|
||||
}
|
||||
|
||||
void test_checkout_checkout__symlinks(void)
|
||||
{
|
||||
/* First try with symlinks forced on */
|
||||
enable_symlinks(true);
|
||||
cl_git_pass(git_checkout_head(g_repo, NULL, NULL));
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
test_file_contents("./testrepo/link_to_new.txt", "new.txt");
|
||||
#else
|
||||
{
|
||||
char link_data[1024];
|
||||
size_t link_size = 1024;
|
||||
|
||||
link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size);
|
||||
link_data[link_size] = '\0';
|
||||
cl_assert_equal_i(link_size, strlen("new.txt"));
|
||||
cl_assert_equal_s(link_data, "new.txt");
|
||||
test_file_contents("./testrepo/link_to_new.txt", "my new file\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Now with symlinks forced off */
|
||||
cl_git_sandbox_cleanup();
|
||||
g_repo = cl_git_sandbox_init("testrepo");
|
||||
enable_symlinks(false);
|
||||
cl_git_pass(git_checkout_head(g_repo, NULL, NULL));
|
||||
|
||||
test_file_contents("./testrepo/link_to_new.txt", "new.txt");
|
||||
}
|
||||
|
||||
void test_checkout_checkout__existing_file_skip(void)
|
||||
{
|
||||
git_checkout_opts opts = {0};
|
||||
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
|
||||
opts.existing_file_action = GIT_CHECKOUT_SKIP_EXISTING;
|
||||
cl_git_pass(git_checkout_head(g_repo, &opts, NULL));
|
||||
test_file_contents("./testrepo/new.txt", "This isn't what's stored!");
|
||||
}
|
||||
|
||||
void test_checkout_checkout__existing_file_overwrite(void)
|
||||
{
|
||||
git_checkout_opts opts = {0};
|
||||
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
|
||||
opts.existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING;
|
||||
cl_git_pass(git_checkout_head(g_repo, &opts, NULL));
|
||||
test_file_contents("./testrepo/new.txt", "my new file\n");
|
||||
}
|
||||
|
||||
void test_checkout_checkout__disable_filters(void)
|
||||
{
|
||||
git_checkout_opts opts = {0};
|
||||
cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n");
|
||||
/* TODO cl_git_pass(git_checkout_head(g_repo, &opts, NULL));*/
|
||||
/* TODO test_file_contents("./testrepo/new.txt", "my new file\r\n");*/
|
||||
opts.disable_filters = true;
|
||||
cl_git_pass(git_checkout_head(g_repo, &opts, NULL));
|
||||
test_file_contents("./testrepo/new.txt", "my new file\n");
|
||||
}
|
||||
|
||||
void test_checkout_checkout__dir_modes(void)
|
||||
{
|
||||
#ifndef GIT_WIN32
|
||||
git_checkout_opts opts = {0};
|
||||
struct stat st;
|
||||
git_reference *ref;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/dir"));
|
||||
|
||||
opts.dir_mode = 0701;
|
||||
cl_git_pass(git_checkout_reference(ref, &opts, NULL));
|
||||
cl_git_pass(p_stat("./testrepo/a", &st));
|
||||
cl_assert_equal_i(st.st_mode & 0777, 0701);
|
||||
|
||||
/* File-mode test, since we're on the 'dir' branch */
|
||||
cl_git_pass(p_stat("./testrepo/a/b.txt", &st));
|
||||
cl_assert_equal_i(st.st_mode & 0777, 0755);
|
||||
|
||||
git_reference_free(ref);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_checkout_checkout__override_file_modes(void)
|
||||
{
|
||||
#ifndef GIT_WIN32
|
||||
git_checkout_opts opts = {0};
|
||||
struct stat st;
|
||||
|
||||
opts.file_mode = 0700;
|
||||
cl_git_pass(git_checkout_head(g_repo, &opts, NULL));
|
||||
cl_git_pass(p_stat("./testrepo/new.txt", &st));
|
||||
cl_assert_equal_i(st.st_mode & 0777, 0700);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_checkout_checkout__open_flags(void)
|
||||
{
|
||||
git_checkout_opts opts = {0};
|
||||
|
||||
cl_git_mkfile("./testrepo/new.txt", "hi\n");
|
||||
opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND;
|
||||
cl_git_pass(git_checkout_head(g_repo, &opts, NULL));
|
||||
test_file_contents("./testrepo/new.txt", "hi\nmy new file\n");
|
||||
}
|
||||
|
||||
void test_checkout_checkout__detached_head(void)
|
||||
{
|
||||
/* TODO: write this when git_checkout_commit is implemented. */
|
||||
}
|
139
tests-clar/clone/clone.c
Normal file
139
tests-clar/clone/clone.c
Normal file
@ -0,0 +1,139 @@
|
||||
#include "clar_libgit2.h"
|
||||
|
||||
#include "git2/clone.h"
|
||||
#include "repository.h"
|
||||
|
||||
#define DO_LOCAL_TEST 0
|
||||
#define DO_LIVE_NETWORK_TESTS 0
|
||||
#define LIVE_REPO_URL "http://github.com/libgit2/node-gitteh"
|
||||
|
||||
|
||||
static git_repository *g_repo;
|
||||
|
||||
void test_clone_clone__initialize(void)
|
||||
{
|
||||
g_repo = NULL;
|
||||
}
|
||||
|
||||
void test_clone_clone__cleanup(void)
|
||||
{
|
||||
if (g_repo) {
|
||||
git_repository_free(g_repo);
|
||||
g_repo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This is copy/pasted from network/remotelocal.c.
|
||||
static void build_local_file_url(git_buf *out, const char *fixture)
|
||||
{
|
||||
const char *in_buf;
|
||||
|
||||
git_buf path_buf = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_path_prettify_dir(&path_buf, fixture, NULL));
|
||||
cl_git_pass(git_buf_puts(out, "file://"));
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
/*
|
||||
* A FILE uri matches the following format: file://[host]/path
|
||||
* where "host" can be empty and "path" is an absolute path to the resource.
|
||||
*
|
||||
* In this test, no hostname is used, but we have to ensure the leading triple slashes:
|
||||
*
|
||||
* *nix: file:///usr/home/...
|
||||
* Windows: file:///C:/Users/...
|
||||
*/
|
||||
cl_git_pass(git_buf_putc(out, '/'));
|
||||
#endif
|
||||
|
||||
in_buf = git_buf_cstr(&path_buf);
|
||||
|
||||
/*
|
||||
* A very hacky Url encoding that only takes care of escaping the spaces
|
||||
*/
|
||||
while (*in_buf) {
|
||||
if (*in_buf == ' ')
|
||||
cl_git_pass(git_buf_puts(out, "%20"));
|
||||
else
|
||||
cl_git_pass(git_buf_putc(out, *in_buf));
|
||||
|
||||
in_buf++;
|
||||
}
|
||||
|
||||
git_buf_free(&path_buf);
|
||||
}
|
||||
|
||||
|
||||
void test_clone_clone__bad_url(void)
|
||||
{
|
||||
/* Clone should clean up the mess if the URL isn't a git repository */
|
||||
cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", NULL, NULL, NULL));
|
||||
cl_assert(!git_path_exists("./foo"));
|
||||
cl_git_fail(git_clone_bare(&g_repo, "not_a_repo", "./foo.git", NULL));
|
||||
cl_assert(!git_path_exists("./foo.git"));
|
||||
}
|
||||
|
||||
|
||||
void test_clone_clone__local(void)
|
||||
{
|
||||
git_buf src = GIT_BUF_INIT;
|
||||
build_local_file_url(&src, cl_fixture("testrepo.git"));
|
||||
|
||||
#if DO_LOCAL_TEST
|
||||
cl_git_pass(git_clone(&g_repo, git_buf_cstr(&src), "./local", NULL, NULL, NULL));
|
||||
git_repository_free(g_repo);
|
||||
git_futils_rmdir_r("./local", GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
cl_git_pass(git_clone_bare(&g_repo, git_buf_cstr(&src), "./local.git", NULL));
|
||||
git_futils_rmdir_r("./local.git", GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
#endif
|
||||
|
||||
git_buf_free(&src);
|
||||
}
|
||||
|
||||
|
||||
void test_clone_clone__network_full(void)
|
||||
{
|
||||
#if DO_LIVE_NETWORK_TESTS
|
||||
git_remote *origin;
|
||||
|
||||
cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./test2", NULL, NULL, NULL));
|
||||
cl_assert(!git_repository_is_bare(g_repo));
|
||||
cl_git_pass(git_remote_load(&origin, g_repo, "origin"));
|
||||
git_futils_rmdir_r("./test2", GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_clone_clone__network_bare(void)
|
||||
{
|
||||
#if DO_LIVE_NETWORK_TESTS
|
||||
git_remote *origin;
|
||||
|
||||
cl_git_pass(git_clone_bare(&g_repo, LIVE_REPO_URL, "test", NULL));
|
||||
cl_assert(git_repository_is_bare(g_repo));
|
||||
cl_git_pass(git_remote_load(&origin, g_repo, "origin"));
|
||||
git_futils_rmdir_r("./test", GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void test_clone_clone__already_exists(void)
|
||||
{
|
||||
#if DO_LIVE_NETWORK_TESTS
|
||||
/* Should pass with existing-but-empty dir */
|
||||
p_mkdir("./foo", GIT_DIR_MODE);
|
||||
cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL));
|
||||
git_repository_free(g_repo); g_repo = NULL;
|
||||
git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
#endif
|
||||
|
||||
/* Should fail with a file */
|
||||
cl_git_mkfile("./foo", "Bar!");
|
||||
cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL));
|
||||
git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
|
||||
/* Should fail with existing-and-nonempty dir */
|
||||
p_mkdir("./foo", GIT_DIR_MODE);
|
||||
cl_git_mkfile("./foo/bar", "Baz!");
|
||||
cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL));
|
||||
git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS);
|
||||
}
|
@ -33,7 +33,7 @@ void test_index_read_tree__read_write_involution(void)
|
||||
|
||||
/* read-tree */
|
||||
git_tree_lookup(&tree, repo, &expected);
|
||||
cl_git_pass(git_index_read_tree(index, tree));
|
||||
cl_git_pass(git_index_read_tree(index, tree, NULL));
|
||||
git_tree_free(tree);
|
||||
|
||||
cl_git_pass(git_tree_create_fromindex(&tree_oid, index));
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "git2/reflog.h"
|
||||
#include "reflog.h"
|
||||
|
||||
static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750";
|
||||
static const char *current_master_tip = "099fabac3a9ea935598528c27f866e34089c2eff";
|
||||
static const char *current_head_target = "refs/heads/master";
|
||||
|
||||
static git_repository *g_repo;
|
||||
|
@ -36,7 +36,7 @@ void test_refs_list__all(void)
|
||||
/* We have exactly 9 refs in total if we include the packed ones:
|
||||
* there is a reference that exists both in the packfile and as
|
||||
* loose, but we only list it once */
|
||||
cl_assert(ref_list.count == 9);
|
||||
cl_assert_equal_i(ref_list.count, 10);
|
||||
|
||||
git_strarray_free(&ref_list);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
[remote "test"]
|
||||
url = git://github.com/libgit2/libgit2
|
||||
|
@ -0,0 +1 @@
|
||||
x<01>ÎQ P¿9Å^@³ÂB!1F½‚'€î¢<C3AE>ÒJ?¼½Õ#ø7™ÉK¦ŸJhM›VE€,³·.3û<>¼§Ş¦ˆ‚ÔuVsHè-;õŠUÆÑÙ,œMˆ’…P³Iɉ&ÎÄ”×<E2809D>ìסŠK»O.2µո$8¤ùN·¡İ—´ë§r„½!½l±CTk»lòUgfˆ0¿ËsêÓG(
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
tests-clar/resources/testrepo/.gitted/refs/heads/dir
Normal file
1
tests-clar/resources/testrepo/.gitted/refs/heads/dir
Normal file
@ -0,0 +1 @@
|
||||
144344043ba4d4a405da03de3844aa829ae8be0e
|
@ -1 +1 @@
|
||||
a65fedf39aefe402d3bb6e24df4d4f5fe4547750
|
||||
099fabac3a9ea935598528c27f866e34089c2eff
|
||||
|
@ -484,7 +484,7 @@ static void fill_index_wth_head_entries(git_repository *repo, git_index *index)
|
||||
cl_git_pass(git_commit_lookup(&commit, repo, &oid));
|
||||
cl_git_pass(git_commit_tree(&tree, commit));
|
||||
|
||||
cl_git_pass(git_index_read_tree(index, tree));
|
||||
cl_git_pass(git_index_read_tree(index, tree, NULL));
|
||||
cl_git_pass(git_index_write(index));
|
||||
|
||||
git_tree_free(tree);
|
||||
|
Loading…
Reference in New Issue
Block a user