mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-06 09:41:04 +00:00
Merge pull request #3436 from pks-t/libgit2-worktree
Worktree implementation
This commit is contained in:
commit
4f9f8e0dc9
@ -245,6 +245,18 @@ GIT_EXTERN(int) git_branch_upstream_name(
|
||||
GIT_EXTERN(int) git_branch_is_head(
|
||||
const git_reference *branch);
|
||||
|
||||
/**
|
||||
* Determine if the current branch is checked out in any linked
|
||||
* repository.
|
||||
*
|
||||
* @param branch Reference to the branch.
|
||||
*
|
||||
* @return 1 if branch is checked out, 0 if it isn't,
|
||||
* error code otherwise.
|
||||
*/
|
||||
GIT_EXTERN(int) git_branch_is_checked_out(
|
||||
const git_reference *branch);
|
||||
|
||||
/**
|
||||
* Return the name of remote that the remote tracking branch belongs to.
|
||||
*
|
||||
|
@ -100,6 +100,7 @@ typedef enum {
|
||||
GITERR_REBASE,
|
||||
GITERR_FILESYSTEM,
|
||||
GITERR_PATCH,
|
||||
GITERR_WORKTREE
|
||||
} git_error_t;
|
||||
|
||||
/**
|
||||
|
@ -35,6 +35,17 @@ GIT_BEGIN_DECL
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_open(git_repository **out, const char *path);
|
||||
/**
|
||||
* Open working tree as a repository
|
||||
*
|
||||
* Open the working directory of the working tree as a normal
|
||||
* repository that can then be worked on.
|
||||
*
|
||||
* @param out Output pointer containing opened repository
|
||||
* @param wt Working tree to open
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_open_from_worktree(git_repository **out, git_worktree *wt);
|
||||
|
||||
/**
|
||||
* Create a "fake" repository to wrap an object database
|
||||
@ -334,6 +345,17 @@ GIT_EXTERN(int) git_repository_init_ext(
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo);
|
||||
|
||||
/**
|
||||
* Retrieve the referenced HEAD for the worktree
|
||||
*
|
||||
* @param out pointer to the reference which will be retrieved
|
||||
* @param repo a repository object
|
||||
* @param name name of the worktree to retrieve HEAD for
|
||||
* @return 0 when successful, error-code otherwise
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_head_for_worktree(git_reference **out, git_repository *repo,
|
||||
const char *name);
|
||||
|
||||
/**
|
||||
* Check if a repository's HEAD is detached
|
||||
*
|
||||
@ -346,6 +368,20 @@ GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo);
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_head_detached(git_repository *repo);
|
||||
|
||||
/*
|
||||
* Check if a worktree's HEAD is detached
|
||||
*
|
||||
* A worktree's HEAD is detached when it points directly to a
|
||||
* commit instead of a branch.
|
||||
*
|
||||
* @param repo a repository object
|
||||
* @param name name of the worktree to retrieve HEAD for
|
||||
* @return 1 if HEAD is detached, 0 if its not; error code if
|
||||
* there was an error
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_head_detached_for_worktree(git_repository *repo,
|
||||
const char *name);
|
||||
|
||||
/**
|
||||
* Check if the current branch is unborn
|
||||
*
|
||||
@ -370,6 +406,42 @@ GIT_EXTERN(int) git_repository_head_unborn(git_repository *repo);
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_is_empty(git_repository *repo);
|
||||
|
||||
/**
|
||||
* List of items which belong to the git repository layout
|
||||
*/
|
||||
typedef enum {
|
||||
GIT_REPOSITORY_ITEM_GITDIR,
|
||||
GIT_REPOSITORY_ITEM_WORKDIR,
|
||||
GIT_REPOSITORY_ITEM_COMMONDIR,
|
||||
GIT_REPOSITORY_ITEM_INDEX,
|
||||
GIT_REPOSITORY_ITEM_OBJECTS,
|
||||
GIT_REPOSITORY_ITEM_REFS,
|
||||
GIT_REPOSITORY_ITEM_PACKED_REFS,
|
||||
GIT_REPOSITORY_ITEM_REMOTES,
|
||||
GIT_REPOSITORY_ITEM_CONFIG,
|
||||
GIT_REPOSITORY_ITEM_INFO,
|
||||
GIT_REPOSITORY_ITEM_HOOKS,
|
||||
GIT_REPOSITORY_ITEM_LOGS,
|
||||
GIT_REPOSITORY_ITEM_MODULES,
|
||||
GIT_REPOSITORY_ITEM_WORKTREES
|
||||
} git_repository_item_t;
|
||||
|
||||
/**
|
||||
* Get the location of a specific repository file or directory
|
||||
*
|
||||
* This function will retrieve the path of a specific repository
|
||||
* item. It will thereby honor things like the repository's
|
||||
* common directory, gitdir, etc. In case a file path cannot
|
||||
* exist for a given item (e.g. the working directory of a bare
|
||||
* repository), an error is returned.
|
||||
*
|
||||
* @param out Buffer to store the path at
|
||||
* @param repo Repository to get path for
|
||||
* @param item The repository item for which to retrieve the path
|
||||
* @return 0 on success, otherwise a negative value
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_item_path(git_buf *out, git_repository *repo, git_repository_item_t item);
|
||||
|
||||
/**
|
||||
* Get the path of this repository
|
||||
*
|
||||
@ -392,6 +464,17 @@ GIT_EXTERN(const char *) git_repository_path(git_repository *repo);
|
||||
*/
|
||||
GIT_EXTERN(const char *) git_repository_workdir(git_repository *repo);
|
||||
|
||||
/**
|
||||
* Get the path of the shared common directory for this repository
|
||||
*
|
||||
* If the repository is bare is not a worktree, the git directory
|
||||
* path is returned.
|
||||
*
|
||||
* @param repo A repository object
|
||||
* @return the path to the common dir
|
||||
*/
|
||||
GIT_EXTERN(const char *) git_repository_commondir(git_repository *repo);
|
||||
|
||||
/**
|
||||
* Set the path to the working directory for this repository
|
||||
*
|
||||
@ -420,6 +503,14 @@ GIT_EXTERN(int) git_repository_set_workdir(
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_is_bare(git_repository *repo);
|
||||
|
||||
/**
|
||||
* Check if a repository is a linked work tree
|
||||
*
|
||||
* @param repo Repo to test
|
||||
* @return 1 if the repository is a linked work tree, 0 otherwise.
|
||||
*/
|
||||
GIT_EXTERN(int) git_repository_is_worktree(git_repository *repo);
|
||||
|
||||
/**
|
||||
* Get the configuration file for this repository.
|
||||
*
|
||||
|
@ -104,6 +104,9 @@ typedef struct git_refdb_backend git_refdb_backend;
|
||||
*/
|
||||
typedef struct git_repository git_repository;
|
||||
|
||||
/** Representation of a working tree */
|
||||
typedef struct git_worktree git_worktree;
|
||||
|
||||
/** Representation of a generic object in a repository */
|
||||
typedef struct git_object git_object;
|
||||
|
||||
|
161
include/git2/worktree.h
Normal file
161
include/git2/worktree.h
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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_worktree_h__
|
||||
#define INCLUDE_git_worktree_h__
|
||||
|
||||
#include "common.h"
|
||||
#include "buffer.h"
|
||||
#include "types.h"
|
||||
#include "strarray.h"
|
||||
|
||||
/**
|
||||
* @file git2/worktrees.h
|
||||
* @brief Git worktree related functions
|
||||
* @defgroup git_commit Git worktree related functions
|
||||
* @ingroup Git
|
||||
* @{
|
||||
*/
|
||||
GIT_BEGIN_DECL
|
||||
|
||||
/**
|
||||
* List names of linked working trees
|
||||
*
|
||||
* The returned list should be released with `git_strarray_free`
|
||||
* when no longer needed.
|
||||
*
|
||||
* @param out pointer to the array of working tree names
|
||||
* @param repo the repo to use when listing working trees
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_worktree_list(git_strarray *out, git_repository *repo);
|
||||
|
||||
/**
|
||||
* Lookup a working tree by its name for a given repository
|
||||
*
|
||||
* @param out Output pointer to looked up worktree or `NULL`
|
||||
* @param repo The repository containing worktrees
|
||||
* @param name Name of the working tree to look up
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name);
|
||||
|
||||
/**
|
||||
* Free a previously allocated worktree
|
||||
*
|
||||
* @param wt worktree handle to close. If NULL nothing occurs.
|
||||
*/
|
||||
GIT_EXTERN(void) git_worktree_free(git_worktree *wt);
|
||||
|
||||
/**
|
||||
* Check if worktree is valid
|
||||
*
|
||||
* A valid worktree requires both the git data structures inside
|
||||
* the linked parent repository and the linked working copy to be
|
||||
* present.
|
||||
*
|
||||
* @param wt Worktree to check
|
||||
* @return 0 when worktree is valid, error-code otherwise
|
||||
*/
|
||||
GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt);
|
||||
|
||||
/**
|
||||
* Add a new working tree
|
||||
*
|
||||
* Add a new working tree for the repository, that is create the
|
||||
* required data structures inside the repository and check out
|
||||
* the current HEAD at `path`
|
||||
*
|
||||
* @param out Output pointer containing new working tree
|
||||
* @param repo Repository to create working tree for
|
||||
* @param name Name of the working tree
|
||||
* @param path Path to create working tree at
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *path);
|
||||
|
||||
/**
|
||||
* Lock worktree if not already locked
|
||||
*
|
||||
* Lock a worktree, optionally specifying a reason why the linked
|
||||
* working tree is being locked.
|
||||
*
|
||||
* @param wt Worktree to lock
|
||||
* @param reason Reason why the working tree is being locked
|
||||
* @return 0 on success, non-zero otherwise
|
||||
*/
|
||||
GIT_EXTERN(int) git_worktree_lock(git_worktree *wt, char *reason);
|
||||
|
||||
/**
|
||||
* Unlock a locked worktree
|
||||
*
|
||||
* @param wt Worktree to unlock
|
||||
* @return 0 on success, 1 if worktree was not locked, error-code
|
||||
* otherwise
|
||||
*/
|
||||
GIT_EXTERN(int) git_worktree_unlock(git_worktree *wt);
|
||||
|
||||
/**
|
||||
* Check if worktree is locked
|
||||
*
|
||||
* A worktree may be locked if the linked working tree is stored
|
||||
* on a portable device which is not available.
|
||||
*
|
||||
* @param reason Buffer to store reason in. If NULL no reason is stored.
|
||||
* @param wt Worktree to check
|
||||
* @return 0 when the working tree not locked, a value greater
|
||||
* than zero if it is locked, less than zero if there was an
|
||||
* error
|
||||
*/
|
||||
GIT_EXTERN(int) git_worktree_is_locked(git_buf *reason, const git_worktree *wt);
|
||||
|
||||
/**
|
||||
* Flags which can be passed to git_worktree_prune to alter its
|
||||
* behavior.
|
||||
*/
|
||||
typedef enum {
|
||||
/* Prune working tree even if working tree is valid */
|
||||
GIT_WORKTREE_PRUNE_VALID = 1u << 0,
|
||||
/* Prune working tree even if it is locked */
|
||||
GIT_WORKTREE_PRUNE_LOCKED = 1u << 1,
|
||||
/* Prune checked out working tree */
|
||||
GIT_WORKTREE_PRUNE_WORKING_TREE = 1u << 2,
|
||||
} git_worktree_prune_t;
|
||||
|
||||
/**
|
||||
* Is the worktree prunable with the given set of flags?
|
||||
*
|
||||
* A worktree is not prunable in the following scenarios:
|
||||
*
|
||||
* - the worktree is linking to a valid on-disk worktree. The
|
||||
* GIT_WORKTREE_PRUNE_VALID flag will cause this check to be
|
||||
* ignored.
|
||||
* - the worktree is not valid but locked. The
|
||||
* GIT_WORKRTEE_PRUNE_LOCKED flag will cause this check to be
|
||||
* ignored.
|
||||
*
|
||||
* If the worktree is not valid and not locked or if the above
|
||||
* flags have been passed in, this function will return a
|
||||
* positive value.
|
||||
*/
|
||||
GIT_EXTERN(int) git_worktree_is_prunable(git_worktree *wt, unsigned flags);
|
||||
|
||||
/**
|
||||
* Prune working tree
|
||||
*
|
||||
* Prune the working tree, that is remove the git data
|
||||
* structures on disk. The repository will only be pruned of
|
||||
* `git_worktree_is_prunable` succeeds.
|
||||
*
|
||||
* @param wt Worktree to prune
|
||||
* @param flags git_worktree_prune_t flags
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
GIT_EXTERN(int) git_worktree_prune(git_worktree *wt, unsigned flags);
|
||||
|
||||
/** @} */
|
||||
GIT_END_DECL
|
||||
#endif
|
36
src/attr.c
36
src/attr.c
@ -292,7 +292,7 @@ static int attr_setup(git_repository *repo, git_attr_session *attr_session)
|
||||
int error = 0;
|
||||
const char *workdir = git_repository_workdir(repo);
|
||||
git_index *idx = NULL;
|
||||
git_buf sys = GIT_BUF_INIT;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
if (attr_session && attr_session->init_setup)
|
||||
return 0;
|
||||
@ -304,40 +304,45 @@ static int attr_setup(git_repository *repo, git_attr_session *attr_session)
|
||||
* definitions will be available for later file parsing
|
||||
*/
|
||||
|
||||
error = system_attr_file(&sys, attr_session);
|
||||
error = system_attr_file(&path, attr_session);
|
||||
|
||||
if (error == 0)
|
||||
error = preload_attr_file(
|
||||
repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr);
|
||||
repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, path.ptr);
|
||||
|
||||
if (error != GIT_ENOTFOUND)
|
||||
return error;
|
||||
|
||||
git_buf_free(&sys);
|
||||
goto out;
|
||||
|
||||
if ((error = preload_attr_file(
|
||||
repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
|
||||
NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
|
||||
return error;
|
||||
goto out;
|
||||
|
||||
if ((error = git_repository_item_path(&path,
|
||||
repo, GIT_REPOSITORY_ITEM_INFO)) < 0)
|
||||
goto out;
|
||||
|
||||
if ((error = preload_attr_file(
|
||||
repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
|
||||
git_repository_path(repo), GIT_ATTR_FILE_INREPO)) < 0)
|
||||
return error;
|
||||
path.ptr, GIT_ATTR_FILE_INREPO)) < 0)
|
||||
goto out;
|
||||
|
||||
if (workdir != NULL &&
|
||||
(error = preload_attr_file(
|
||||
repo, attr_session, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0)
|
||||
return error;
|
||||
goto out;
|
||||
|
||||
if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
|
||||
(error = preload_attr_file(
|
||||
repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0)
|
||||
return error;
|
||||
goto out;
|
||||
|
||||
if (attr_session)
|
||||
attr_session->init_setup = 1;
|
||||
|
||||
out:
|
||||
git_buf_free(&path);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -472,7 +477,7 @@ static int collect_attr_files(
|
||||
git_vector *files)
|
||||
{
|
||||
int error = 0;
|
||||
git_buf dir = GIT_BUF_INIT;
|
||||
git_buf dir = GIT_BUF_INIT, attrfile = GIT_BUF_INIT;
|
||||
const char *workdir = git_repository_workdir(repo);
|
||||
attr_walk_up_info info = { NULL };
|
||||
|
||||
@ -494,9 +499,13 @@ static int collect_attr_files(
|
||||
* - $GIT_PREFIX/etc/gitattributes
|
||||
*/
|
||||
|
||||
error = git_repository_item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
error = push_attr_file(
|
||||
repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
|
||||
git_repository_path(repo), GIT_ATTR_FILE_INREPO);
|
||||
attrfile.ptr, GIT_ATTR_FILE_INREPO);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
@ -538,6 +547,7 @@ static int collect_attr_files(
|
||||
cleanup:
|
||||
if (error < 0)
|
||||
release_attr_files(files);
|
||||
git_buf_free(&attrfile);
|
||||
git_buf_free(&dir);
|
||||
|
||||
return error;
|
||||
|
@ -15,7 +15,7 @@
|
||||
#include "fileops.h"
|
||||
|
||||
#define GIT_ATTR_FILE ".gitattributes"
|
||||
#define GIT_ATTR_FILE_INREPO "info/attributes"
|
||||
#define GIT_ATTR_FILE_INREPO "attributes"
|
||||
#define GIT_ATTR_FILE_SYSTEM "gitattributes"
|
||||
#define GIT_ATTR_FILE_XDG "attributes"
|
||||
|
||||
|
@ -326,8 +326,8 @@ int git_blob_create_fromstream(git_writestream **out, git_repository *repo, cons
|
||||
stream->parent.close = blob_writestream_close;
|
||||
stream->parent.free = blob_writestream_free;
|
||||
|
||||
if ((error = git_buf_joinpath(&path,
|
||||
git_repository_path(repo), GIT_OBJECTS_DIR "streamed")) < 0)
|
||||
if ((error = git_repository_item_path(&path, repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0
|
||||
|| (error = git_buf_joinpath(&path, path.ptr, "streamed")) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if ((error = git_filebuf_open_withsize(&stream->fbuf, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY,
|
||||
|
63
src/branch.c
63
src/branch.c
@ -13,6 +13,7 @@
|
||||
#include "refs.h"
|
||||
#include "remote.h"
|
||||
#include "annotated_commit.h"
|
||||
#include "worktree.h"
|
||||
|
||||
#include "git2/branch.h"
|
||||
|
||||
@ -126,6 +127,62 @@ int git_branch_create_from_annotated(
|
||||
repository, branch_name, commit->commit, commit->description, force);
|
||||
}
|
||||
|
||||
int git_branch_is_checked_out(
|
||||
const git_reference *branch)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT;
|
||||
git_strarray worktrees;
|
||||
git_reference *ref = NULL;
|
||||
git_repository *repo;
|
||||
const char *worktree;
|
||||
int found = false;
|
||||
size_t i;
|
||||
|
||||
assert(branch && git_reference_is_branch(branch));
|
||||
|
||||
repo = git_reference_owner(branch);
|
||||
|
||||
if (git_worktree_list(&worktrees, repo) < 0)
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < worktrees.count; i++) {
|
||||
worktree = worktrees.strings[i];
|
||||
|
||||
if (git_repository_head_for_worktree(&ref, repo, worktree) < 0)
|
||||
continue;
|
||||
|
||||
if (git__strcmp(ref->name, branch->name) == 0) {
|
||||
found = true;
|
||||
git_reference_free(ref);
|
||||
break;
|
||||
}
|
||||
|
||||
git_reference_free(ref);
|
||||
}
|
||||
git_strarray_free(&worktrees);
|
||||
|
||||
if (found)
|
||||
return found;
|
||||
|
||||
/* Check HEAD of parent */
|
||||
if (git_buf_joinpath(&path, repo->commondir, GIT_HEAD_FILE) < 0)
|
||||
goto out;
|
||||
if (git_futils_readbuffer(&buf, path.ptr) < 0)
|
||||
goto out;
|
||||
if (git__prefixcmp(buf.ptr, "ref: ") == 0)
|
||||
git_buf_consume(&buf, buf.ptr + strlen("ref: "));
|
||||
git_buf_rtrim(&buf);
|
||||
|
||||
found = git__strcmp(buf.ptr, branch->name) == 0;
|
||||
|
||||
out:
|
||||
git_buf_free(&buf);
|
||||
git_buf_free(&path);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
int git_branch_delete(git_reference *branch)
|
||||
{
|
||||
int is_head;
|
||||
@ -149,6 +206,12 @@ int git_branch_delete(git_reference *branch)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (git_reference_is_branch(branch) && git_branch_is_checked_out(branch)) {
|
||||
giterr_set(GITERR_REFERENCE, "Cannot delete branch '%s' as it is "
|
||||
"the current HEAD of a linked repository.", git_reference_name(branch));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (git_buf_join(&config_section, '.', "branch",
|
||||
git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
|
||||
goto on_error;
|
||||
|
@ -28,7 +28,7 @@ static int write_cherrypick_head(
|
||||
git_buf file_path = GIT_BUF_INIT;
|
||||
int error = 0;
|
||||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_CHERRYPICK_HEAD_FILE)) >= 0 &&
|
||||
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_CHERRYPICK_HEAD_FILE)) >= 0 &&
|
||||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) >= 0 &&
|
||||
(error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0)
|
||||
error = git_filebuf_commit(&file);
|
||||
@ -49,7 +49,7 @@ static int write_merge_msg(
|
||||
git_buf file_path = GIT_BUF_INIT;
|
||||
int error = 0;
|
||||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 ||
|
||||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) < 0 ||
|
||||
(error = git_filebuf_printf(&file, "%s", commit_msg)) < 0)
|
||||
goto cleanup;
|
||||
|
@ -513,9 +513,8 @@ static int clone_local_into(git_repository *repo, git_remote *remote, const git_
|
||||
return error;
|
||||
}
|
||||
|
||||
git_buf_joinpath(&src_odb, git_repository_path(src), GIT_OBJECTS_DIR);
|
||||
git_buf_joinpath(&dst_odb, git_repository_path(repo), GIT_OBJECTS_DIR);
|
||||
if (git_buf_oom(&src_odb) || git_buf_oom(&dst_odb)) {
|
||||
if (git_repository_item_path(&src_odb, src, GIT_REPOSITORY_ITEM_OBJECTS) < 0
|
||||
|| git_repository_item_path(&dst_odb, repo, GIT_REPOSITORY_ITEM_OBJECTS) < 0) {
|
||||
error = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs)
|
||||
|
||||
assert(repo && fetchhead_refs);
|
||||
|
||||
if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0)
|
||||
if (git_buf_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE) < 0) {
|
||||
@ -249,7 +249,7 @@ int git_repository_fetchhead_foreach(git_repository *repo,
|
||||
|
||||
assert(repo && cb);
|
||||
|
||||
if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0)
|
||||
if (git_buf_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0)
|
||||
return -1;
|
||||
|
||||
if ((error = git_futils_readbuffer(&file, git_buf_cstr(&path))) < 0)
|
||||
|
@ -277,6 +277,7 @@ int git_ignore__for_path(
|
||||
{
|
||||
int error = 0;
|
||||
const char *workdir = git_repository_workdir(repo);
|
||||
git_buf infopath = GIT_BUF_INIT;
|
||||
|
||||
assert(repo && ignores && path);
|
||||
|
||||
@ -322,10 +323,14 @@ int git_ignore__for_path(
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if ((error = git_repository_item_path(&infopath,
|
||||
repo, GIT_REPOSITORY_ITEM_INFO)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
/* load .git/info/exclude */
|
||||
error = push_ignore_file(
|
||||
ignores, &ignores->ign_global,
|
||||
git_repository_path(repo), GIT_IGNORE_FILE_INREPO);
|
||||
infopath.ptr, GIT_IGNORE_FILE_INREPO);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
@ -336,6 +341,7 @@ int git_ignore__for_path(
|
||||
git_repository_attr_cache(repo)->cfg_excl_file);
|
||||
|
||||
cleanup:
|
||||
git_buf_free(&infopath);
|
||||
if (error < 0)
|
||||
git_ignore__free(ignores);
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include "attr_file.h"
|
||||
|
||||
#define GIT_IGNORE_FILE ".gitignore"
|
||||
#define GIT_IGNORE_FILE_INREPO "info/exclude"
|
||||
#define GIT_IGNORE_FILE_INREPO "exclude"
|
||||
#define GIT_IGNORE_FILE_XDG "ignore"
|
||||
|
||||
/* The git_ignores structure maintains three sets of ignores:
|
||||
|
10
src/merge.c
10
src/merge.c
@ -562,7 +562,7 @@ int git_repository_mergehead_foreach(
|
||||
|
||||
assert(repo && cb);
|
||||
|
||||
if ((error = git_buf_joinpath(&merge_head_path, repo->path_repository,
|
||||
if ((error = git_buf_joinpath(&merge_head_path, repo->gitdir,
|
||||
GIT_MERGE_HEAD_FILE)) < 0)
|
||||
return error;
|
||||
|
||||
@ -2277,7 +2277,7 @@ static int write_merge_head(
|
||||
|
||||
assert(repo && heads);
|
||||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_HEAD_FILE)) < 0 ||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_HEAD_FILE)) < 0 ||
|
||||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
@ -2305,7 +2305,7 @@ static int write_merge_mode(git_repository *repo)
|
||||
|
||||
assert(repo);
|
||||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MODE_FILE)) < 0 ||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MODE_FILE)) < 0 ||
|
||||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
@ -2536,7 +2536,7 @@ static int write_merge_msg(
|
||||
for (i = 0; i < heads_len; i++)
|
||||
entries[i].merge_head = heads[i];
|
||||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 ||
|
||||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0 ||
|
||||
(error = git_filebuf_write(&file, "Merge ", 6)) < 0)
|
||||
goto cleanup;
|
||||
@ -2914,7 +2914,7 @@ int git_merge__append_conflicts_to_merge_msg(
|
||||
if (!git_index_has_conflicts(index))
|
||||
return 0;
|
||||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 ||
|
||||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_APPEND, GIT_MERGE_FILE_MODE)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
|
@ -92,7 +92,7 @@ static int rebase_state_type(
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
git_rebase_type_t type = GIT_REBASE_TYPE_NONE;
|
||||
|
||||
if (git_buf_joinpath(&path, repo->path_repository, REBASE_APPLY_DIR) < 0)
|
||||
if (git_buf_joinpath(&path, repo->gitdir, REBASE_APPLY_DIR) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_path_isdir(git_buf_cstr(&path))) {
|
||||
@ -101,7 +101,7 @@ static int rebase_state_type(
|
||||
}
|
||||
|
||||
git_buf_clear(&path);
|
||||
if (git_buf_joinpath(&path, repo->path_repository, REBASE_MERGE_DIR) < 0)
|
||||
if (git_buf_joinpath(&path, repo->gitdir, REBASE_MERGE_DIR) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_path_isdir(git_buf_cstr(&path))) {
|
||||
@ -624,7 +624,7 @@ static int rebase_init_merge(
|
||||
|
||||
GIT_UNUSED(upstream);
|
||||
|
||||
if ((error = git_buf_joinpath(&state_path, repo->path_repository, REBASE_MERGE_DIR)) < 0)
|
||||
if ((error = git_buf_joinpath(&state_path, repo->gitdir, REBASE_MERGE_DIR)) < 0)
|
||||
goto done;
|
||||
|
||||
rebase->state_path = git_buf_detach(&state_path);
|
||||
|
@ -55,7 +55,10 @@ typedef struct refdb_fs_backend {
|
||||
git_refdb_backend parent;
|
||||
|
||||
git_repository *repo;
|
||||
char *path;
|
||||
/* path to git directory */
|
||||
char *gitpath;
|
||||
/* path to common objects' directory */
|
||||
char *commonpath;
|
||||
|
||||
git_sortedcache *refcache;
|
||||
int peeling_mode;
|
||||
@ -77,7 +80,7 @@ static int packed_reload(refdb_fs_backend *backend)
|
||||
git_buf packedrefs = GIT_BUF_INIT;
|
||||
char *scan, *eof, *eol;
|
||||
|
||||
if (!backend->path)
|
||||
if (!backend->gitpath)
|
||||
return 0;
|
||||
|
||||
error = git_sortedcache_lockandload(backend->refcache, &packedrefs);
|
||||
@ -238,7 +241,7 @@ static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name)
|
||||
/* if we fail to load the loose reference, assume someone changed
|
||||
* the filesystem under us and skip it...
|
||||
*/
|
||||
if (loose_readbuffer(&ref_file, backend->path, name) < 0) {
|
||||
if (loose_readbuffer(&ref_file, backend->gitpath, name) < 0) {
|
||||
giterr_clear();
|
||||
goto done;
|
||||
}
|
||||
@ -287,7 +290,7 @@ static int _dirent_loose_load(void *payload, git_buf *full_path)
|
||||
return error;
|
||||
}
|
||||
|
||||
file_path = full_path->ptr + strlen(backend->path);
|
||||
file_path = full_path->ptr + strlen(backend->gitpath);
|
||||
|
||||
return loose_lookup_to_packfile(backend, file_path);
|
||||
}
|
||||
@ -303,7 +306,7 @@ static int packed_loadloose(refdb_fs_backend *backend)
|
||||
int error;
|
||||
git_buf refs_path = GIT_BUF_INIT;
|
||||
|
||||
if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)
|
||||
if (git_buf_joinpath(&refs_path, backend->gitpath, GIT_REFS_DIR) < 0)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
@ -331,7 +334,7 @@ static int refdb_fs_backend__exists(
|
||||
assert(backend);
|
||||
|
||||
if ((error = packed_reload(backend)) < 0 ||
|
||||
(error = git_buf_joinpath(&ref_path, backend->path, ref_name)) < 0)
|
||||
(error = git_buf_joinpath(&ref_path, backend->gitpath, ref_name)) < 0)
|
||||
return error;
|
||||
|
||||
*exists = git_path_isfile(ref_path.ptr) ||
|
||||
@ -362,6 +365,14 @@ static const char *loose_parse_symbolic(git_buf *file_content)
|
||||
return refname_start;
|
||||
}
|
||||
|
||||
static bool is_per_worktree_ref(const char *ref_name)
|
||||
{
|
||||
return strcmp("HEAD", ref_name) == 0 ||
|
||||
strcmp("FETCH_HEAD", ref_name) == 0 ||
|
||||
strcmp("MERGE_HEAD", ref_name) == 0 ||
|
||||
strcmp("ORIG_HEAD", ref_name) == 0;
|
||||
}
|
||||
|
||||
static int loose_lookup(
|
||||
git_reference **out,
|
||||
refdb_fs_backend *backend,
|
||||
@ -369,11 +380,17 @@ static int loose_lookup(
|
||||
{
|
||||
git_buf ref_file = GIT_BUF_INIT;
|
||||
int error = 0;
|
||||
const char *ref_dir;
|
||||
|
||||
if (out)
|
||||
*out = NULL;
|
||||
|
||||
if ((error = loose_readbuffer(&ref_file, backend->path, ref_name)) < 0)
|
||||
if (is_per_worktree_ref(ref_name))
|
||||
ref_dir = backend->gitpath;
|
||||
else
|
||||
ref_dir = backend->commonpath;
|
||||
|
||||
if ((error = loose_readbuffer(&ref_file, ref_dir, ref_name)) < 0)
|
||||
/* cannot read loose ref file - gah */;
|
||||
else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) {
|
||||
const char *target;
|
||||
@ -484,12 +501,12 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
|
||||
git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT;
|
||||
const git_index_entry *entry = NULL;
|
||||
|
||||
if (!backend->path) /* do nothing if no path for loose refs */
|
||||
if (!backend->commonpath) /* do nothing if no commonpath for loose refs */
|
||||
return 0;
|
||||
|
||||
fsit_opts.flags = backend->iterator_flags;
|
||||
|
||||
if ((error = git_buf_printf(&path, "%s/refs", backend->path)) < 0 ||
|
||||
if ((error = git_buf_printf(&path, "%s/refs", backend->commonpath)) < 0 ||
|
||||
(error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) {
|
||||
git_buf_free(&path);
|
||||
return error;
|
||||
@ -729,10 +746,10 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *
|
||||
/* Remove a possibly existing empty directory hierarchy
|
||||
* which name would collide with the reference name
|
||||
*/
|
||||
if ((error = git_futils_rmdir_r(name, backend->path, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
|
||||
if ((error = git_futils_rmdir_r(name, backend->gitpath, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
|
||||
return error;
|
||||
|
||||
if (git_buf_joinpath(&ref_path, backend->path, name) < 0)
|
||||
if (git_buf_joinpath(&ref_path, backend->gitpath, name) < 0)
|
||||
return -1;
|
||||
|
||||
error = git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE);
|
||||
@ -1283,7 +1300,7 @@ static int refdb_fs_backend__delete_tail(
|
||||
}
|
||||
|
||||
/* If a loose reference exists, remove it from the filesystem */
|
||||
if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0)
|
||||
if (git_buf_joinpath(&loose_path, backend->gitpath, ref_name) < 0)
|
||||
return -1;
|
||||
|
||||
|
||||
@ -1408,20 +1425,23 @@ static void refdb_fs_backend__free(git_refdb_backend *_backend)
|
||||
assert(backend);
|
||||
|
||||
git_sortedcache_free(backend->refcache);
|
||||
git__free(backend->path);
|
||||
git__free(backend->gitpath);
|
||||
git__free(backend->commonpath);
|
||||
git__free(backend);
|
||||
}
|
||||
|
||||
static int setup_namespace(git_buf *path, git_repository *repo)
|
||||
static int setup_namespace(git_buf *gitpath, git_repository *repo)
|
||||
{
|
||||
char *parts, *start, *end;
|
||||
|
||||
/* Not all repositories have a path */
|
||||
if (repo->path_repository == NULL)
|
||||
/* Not all repositories have a gitpath */
|
||||
if (repo->gitdir == NULL)
|
||||
return 0;
|
||||
if (repo->commondir == NULL)
|
||||
return 0;
|
||||
|
||||
/* Load the path to the repo first */
|
||||
git_buf_puts(path, repo->path_repository);
|
||||
git_buf_puts(gitpath, repo->gitdir);
|
||||
|
||||
/* if the repo is not namespaced, nothing else to do */
|
||||
if (repo->namespace == NULL)
|
||||
@ -1438,19 +1458,19 @@ static int setup_namespace(git_buf *path, git_repository *repo)
|
||||
* refs under refs/namespaces/foo/refs/namespaces/bar/
|
||||
*/
|
||||
while ((start = git__strsep(&end, "/")) != NULL) {
|
||||
git_buf_printf(path, "refs/namespaces/%s/", start);
|
||||
git_buf_printf(gitpath, "refs/namespaces/%s/", start);
|
||||
}
|
||||
|
||||
git_buf_printf(path, "refs/namespaces/%s/refs", end);
|
||||
git_buf_printf(gitpath, "refs/namespaces/%s/refs", end);
|
||||
git__free(parts);
|
||||
|
||||
/* Make sure that the folder with the namespace exists */
|
||||
if (git_futils_mkdir_relative(git_buf_cstr(path), repo->path_repository,
|
||||
if (git_futils_mkdir_relative(git_buf_cstr(gitpath), repo->commondir,
|
||||
0777, GIT_MKDIR_PATH, NULL) < 0)
|
||||
return -1;
|
||||
|
||||
/* Return root of the namespaced path, i.e. without the trailing '/refs' */
|
||||
git_buf_rtruncate_at_char(path, '/');
|
||||
/* Return root of the namespaced gitpath, i.e. without the trailing '/refs' */
|
||||
git_buf_rtruncate_at_char(gitpath, '/');
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1562,7 +1582,7 @@ static int create_new_reflog_file(const char *filepath)
|
||||
|
||||
GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name)
|
||||
{
|
||||
return git_buf_join3(path, '/', repo->path_repository, GIT_REFLOG_DIR, name);
|
||||
return git_buf_join3(path, '/', repo->commondir, GIT_REFLOG_DIR, name);
|
||||
}
|
||||
|
||||
static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name)
|
||||
@ -1857,7 +1877,7 @@ static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_
|
||||
&normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0)
|
||||
return error;
|
||||
|
||||
if (git_buf_joinpath(&temp_path, repo->path_repository, GIT_REFLOG_DIR) < 0)
|
||||
if (git_buf_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0)
|
||||
@ -1948,7 +1968,7 @@ int git_refdb_backend_fs(
|
||||
git_repository *repository)
|
||||
{
|
||||
int t = 0;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
git_buf gitpath = GIT_BUF_INIT;
|
||||
refdb_fs_backend *backend;
|
||||
|
||||
backend = git__calloc(1, sizeof(refdb_fs_backend));
|
||||
@ -1956,18 +1976,20 @@ int git_refdb_backend_fs(
|
||||
|
||||
backend->repo = repository;
|
||||
|
||||
if (setup_namespace(&path, repository) < 0)
|
||||
if (setup_namespace(&gitpath, repository) < 0)
|
||||
goto fail;
|
||||
|
||||
backend->path = git_buf_detach(&path);
|
||||
backend->gitpath = backend->commonpath = git_buf_detach(&gitpath);
|
||||
if (repository->commondir)
|
||||
backend->commonpath = git__strdup(repository->commondir);
|
||||
|
||||
if (git_buf_joinpath(&path, backend->path, GIT_PACKEDREFS_FILE) < 0 ||
|
||||
if (git_buf_joinpath(&gitpath, backend->commonpath, GIT_PACKEDREFS_FILE) < 0 ||
|
||||
git_sortedcache_new(
|
||||
&backend->refcache, offsetof(struct packref, name),
|
||||
NULL, NULL, packref_cmp, git_buf_cstr(&path)) < 0)
|
||||
NULL, NULL, packref_cmp, git_buf_cstr(&gitpath)) < 0)
|
||||
goto fail;
|
||||
|
||||
git_buf_free(&path);
|
||||
git_buf_free(&gitpath);
|
||||
|
||||
if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) {
|
||||
backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE;
|
||||
@ -1999,8 +2021,9 @@ int git_refdb_backend_fs(
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
git_buf_free(&path);
|
||||
git__free(backend->path);
|
||||
git_buf_free(&gitpath);
|
||||
git__free(backend->gitpath);
|
||||
git__free(backend->commonpath);
|
||||
git__free(backend);
|
||||
return -1;
|
||||
}
|
||||
|
359
src/repository.c
359
src/repository.c
@ -28,6 +28,7 @@
|
||||
#include "diff_driver.h"
|
||||
#include "annotated_commit.h"
|
||||
#include "submodule.h"
|
||||
#include "worktree.h"
|
||||
|
||||
GIT__USE_STRMAP
|
||||
#include "strmap.h"
|
||||
@ -36,8 +37,32 @@ GIT__USE_STRMAP
|
||||
# include "win32/w32_util.h"
|
||||
#endif
|
||||
|
||||
static const struct {
|
||||
git_repository_item_t parent;
|
||||
const char *name;
|
||||
bool directory;
|
||||
} items[] = {
|
||||
{ GIT_REPOSITORY_ITEM_GITDIR, NULL, true },
|
||||
{ GIT_REPOSITORY_ITEM_WORKDIR, NULL, true },
|
||||
{ GIT_REPOSITORY_ITEM_COMMONDIR, NULL, true },
|
||||
{ GIT_REPOSITORY_ITEM_GITDIR, "index", false },
|
||||
{ GIT_REPOSITORY_ITEM_COMMONDIR, "objects", true },
|
||||
{ GIT_REPOSITORY_ITEM_COMMONDIR, "refs", true },
|
||||
{ GIT_REPOSITORY_ITEM_COMMONDIR, "packed-refs", false },
|
||||
{ GIT_REPOSITORY_ITEM_COMMONDIR, "remotes", true },
|
||||
{ GIT_REPOSITORY_ITEM_COMMONDIR, "config", false },
|
||||
{ GIT_REPOSITORY_ITEM_COMMONDIR, "info", true },
|
||||
{ GIT_REPOSITORY_ITEM_COMMONDIR, "hooks", true },
|
||||
{ GIT_REPOSITORY_ITEM_COMMONDIR, "logs", true },
|
||||
{ GIT_REPOSITORY_ITEM_GITDIR, "modules", true },
|
||||
{ GIT_REPOSITORY_ITEM_COMMONDIR, "worktrees", true }
|
||||
};
|
||||
|
||||
static int check_repositoryformatversion(git_config *config);
|
||||
|
||||
#define GIT_COMMONDIR_FILE "commondir"
|
||||
#define GIT_GITDIR_FILE "gitdir"
|
||||
|
||||
#define GIT_FILE_CONTENT_PREFIX "gitdir:"
|
||||
|
||||
#define GIT_BRANCH_MASTER "master"
|
||||
@ -141,8 +166,9 @@ void git_repository_free(git_repository *repo)
|
||||
git_buf_free(git_array_get(repo->reserved_names, i));
|
||||
git_array_clear(repo->reserved_names);
|
||||
|
||||
git__free(repo->path_gitlink);
|
||||
git__free(repo->path_repository);
|
||||
git__free(repo->gitlink);
|
||||
git__free(repo->gitdir);
|
||||
git__free(repo->commondir);
|
||||
git__free(repo->workdir);
|
||||
git__free(repo->namespace);
|
||||
git__free(repo->ident_name);
|
||||
@ -157,17 +183,41 @@ void git_repository_free(git_repository *repo)
|
||||
*
|
||||
* Open a repository object from its path
|
||||
*/
|
||||
static bool valid_repository_path(git_buf *repository_path)
|
||||
static bool valid_repository_path(git_buf *repository_path, git_buf *common_path)
|
||||
{
|
||||
/* Check OBJECTS_DIR first, since it will generate the longest path name */
|
||||
if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false)
|
||||
return false;
|
||||
/* Check if we have a separate commondir (e.g. we have a
|
||||
* worktree) */
|
||||
if (git_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) {
|
||||
git_buf common_link = GIT_BUF_INIT;
|
||||
git_buf_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE);
|
||||
|
||||
git_futils_readbuffer(&common_link, common_link.ptr);
|
||||
git_buf_rtrim(&common_link);
|
||||
|
||||
if (git_path_is_relative(common_link.ptr)) {
|
||||
git_buf_joinpath(common_path, repository_path->ptr, common_link.ptr);
|
||||
} else {
|
||||
git_buf_swap(common_path, &common_link);
|
||||
}
|
||||
|
||||
git_buf_free(&common_link);
|
||||
}
|
||||
else {
|
||||
git_buf_set(common_path, repository_path->ptr, repository_path->size);
|
||||
}
|
||||
|
||||
/* Make sure the commondir path always has a trailing * slash */
|
||||
if (git_buf_rfind(common_path, '/') != (ssize_t)common_path->size - 1)
|
||||
git_buf_putc(common_path, '/');
|
||||
|
||||
/* Ensure HEAD file exists */
|
||||
if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false)
|
||||
return false;
|
||||
|
||||
if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false)
|
||||
/* Check files in common dir */
|
||||
if (git_path_contains_dir(common_path, GIT_OBJECTS_DIR) == false)
|
||||
return false;
|
||||
if (git_path_contains_dir(common_path, GIT_REFS_DIR) == false)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@ -206,6 +256,7 @@ int git_repository_new(git_repository **out)
|
||||
GITERR_CHECK_ALLOC(repo);
|
||||
|
||||
repo->is_bare = 1;
|
||||
repo->is_worktree = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -228,6 +279,7 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren
|
||||
int error;
|
||||
git_config_entry *ce;
|
||||
git_buf worktree = GIT_BUF_INIT;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
if (repo->is_bare)
|
||||
return 0;
|
||||
@ -236,9 +288,26 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren
|
||||
&ce, config, "core.worktree", false)) < 0)
|
||||
return error;
|
||||
|
||||
if (ce && ce->value) {
|
||||
if (repo->is_worktree) {
|
||||
char *gitlink = git_worktree__read_link(repo->gitdir, GIT_GITDIR_FILE);
|
||||
if (!gitlink) {
|
||||
error = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
git_buf_attach(&worktree, gitlink, 0);
|
||||
|
||||
if ((git_path_dirname_r(&worktree, worktree.ptr)) < 0 ||
|
||||
git_path_to_dir(&worktree) < 0) {
|
||||
error = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
repo->workdir = git_buf_detach(&worktree);
|
||||
}
|
||||
else if (ce && ce->value) {
|
||||
if ((error = git_path_prettify_dir(
|
||||
&worktree, ce->value, repo->path_repository)) < 0)
|
||||
&worktree, ce->value, repo->gitdir)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
repo->workdir = git_buf_detach(&worktree);
|
||||
@ -246,7 +315,7 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren
|
||||
else if (parent_path && git_path_isdir(parent_path->ptr))
|
||||
repo->workdir = git_buf_detach(parent_path);
|
||||
else {
|
||||
if (git_path_dirname_r(&worktree, repo->path_repository) < 0 ||
|
||||
if (git_path_dirname_r(&worktree, repo->gitdir) < 0 ||
|
||||
git_path_to_dir(&worktree) < 0) {
|
||||
error = -1;
|
||||
goto cleanup;
|
||||
@ -257,6 +326,7 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren
|
||||
|
||||
GITERR_CHECK_ALLOC(repo->workdir);
|
||||
cleanup:
|
||||
git_buf_free(&path);
|
||||
git_config_entry_free(ce);
|
||||
return error;
|
||||
}
|
||||
@ -356,6 +426,7 @@ static int find_repo(
|
||||
git_buf *repo_path,
|
||||
git_buf *parent_path,
|
||||
git_buf *link_path,
|
||||
git_buf *common_path,
|
||||
const char *start_path,
|
||||
uint32_t flags,
|
||||
const char *ceiling_dirs)
|
||||
@ -363,6 +434,7 @@ static int find_repo(
|
||||
int error;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
git_buf repo_link = GIT_BUF_INIT;
|
||||
git_buf common_link = GIT_BUF_INIT;
|
||||
struct stat st;
|
||||
dev_t initial_device = 0;
|
||||
int min_iterations;
|
||||
@ -409,9 +481,16 @@ static int find_repo(
|
||||
break;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
if (valid_repository_path(&path)) {
|
||||
if (valid_repository_path(&path, &common_link)) {
|
||||
git_path_to_dir(&path);
|
||||
git_buf_set(repo_path, path.ptr, path.size);
|
||||
|
||||
if (link_path)
|
||||
git_buf_attach(link_path,
|
||||
git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0);
|
||||
if (common_path)
|
||||
git_buf_swap(&common_link, common_path);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -419,11 +498,13 @@ static int find_repo(
|
||||
error = read_gitfile(&repo_link, path.ptr);
|
||||
if (error < 0)
|
||||
break;
|
||||
if (valid_repository_path(&repo_link)) {
|
||||
if (valid_repository_path(&repo_link, &common_link)) {
|
||||
git_buf_swap(repo_path, &repo_link);
|
||||
|
||||
if (link_path)
|
||||
error = git_buf_put(link_path, path.ptr, path.size);
|
||||
if (common_path)
|
||||
git_buf_swap(&common_link, common_path);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -470,6 +551,7 @@ static int find_repo(
|
||||
|
||||
git_buf_free(&path);
|
||||
git_buf_free(&repo_link);
|
||||
git_buf_free(&common_link);
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -478,14 +560,15 @@ int git_repository_open_bare(
|
||||
const char *bare_path)
|
||||
{
|
||||
int error;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
git_buf path = GIT_BUF_INIT, common_path = GIT_BUF_INIT;
|
||||
git_repository *repo = NULL;
|
||||
|
||||
if ((error = git_path_prettify_dir(&path, bare_path, NULL)) < 0)
|
||||
return error;
|
||||
|
||||
if (!valid_repository_path(&path)) {
|
||||
if (!valid_repository_path(&path, &common_path)) {
|
||||
git_buf_free(&path);
|
||||
git_buf_free(&common_path);
|
||||
giterr_set(GITERR_REPOSITORY, "path is not a repository: %s", bare_path);
|
||||
return GIT_ENOTFOUND;
|
||||
}
|
||||
@ -493,11 +576,14 @@ int git_repository_open_bare(
|
||||
repo = repository_alloc();
|
||||
GITERR_CHECK_ALLOC(repo);
|
||||
|
||||
repo->path_repository = git_buf_detach(&path);
|
||||
GITERR_CHECK_ALLOC(repo->path_repository);
|
||||
repo->gitdir = git_buf_detach(&path);
|
||||
GITERR_CHECK_ALLOC(repo->gitdir);
|
||||
repo->commondir = git_buf_detach(&common_path);
|
||||
GITERR_CHECK_ALLOC(repo->commondir);
|
||||
|
||||
/* of course we're bare! */
|
||||
repo->is_bare = 1;
|
||||
repo->is_worktree = 0;
|
||||
repo->workdir = NULL;
|
||||
|
||||
*repo_ptr = repo;
|
||||
@ -681,7 +767,7 @@ int git_repository_open_ext(
|
||||
{
|
||||
int error;
|
||||
git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT,
|
||||
link_path = GIT_BUF_INIT;
|
||||
link_path = GIT_BUF_INIT, common_path = GIT_BUF_INIT;
|
||||
git_repository *repo;
|
||||
git_config *config = NULL;
|
||||
|
||||
@ -692,7 +778,7 @@ int git_repository_open_ext(
|
||||
*repo_ptr = NULL;
|
||||
|
||||
error = find_repo(
|
||||
&path, &parent, &link_path, start_path, flags, ceiling_dirs);
|
||||
&path, &parent, &link_path, &common_path, start_path, flags, ceiling_dirs);
|
||||
|
||||
if (error < 0 || !repo_ptr)
|
||||
return error;
|
||||
@ -700,13 +786,24 @@ int git_repository_open_ext(
|
||||
repo = repository_alloc();
|
||||
GITERR_CHECK_ALLOC(repo);
|
||||
|
||||
repo->path_repository = git_buf_detach(&path);
|
||||
GITERR_CHECK_ALLOC(repo->path_repository);
|
||||
repo->gitdir = git_buf_detach(&path);
|
||||
GITERR_CHECK_ALLOC(repo->gitdir);
|
||||
|
||||
if (link_path.size) {
|
||||
repo->path_gitlink = git_buf_detach(&link_path);
|
||||
GITERR_CHECK_ALLOC(repo->path_gitlink);
|
||||
repo->gitlink = git_buf_detach(&link_path);
|
||||
GITERR_CHECK_ALLOC(repo->gitlink);
|
||||
}
|
||||
if (common_path.size) {
|
||||
repo->commondir = git_buf_detach(&common_path);
|
||||
GITERR_CHECK_ALLOC(repo->commondir);
|
||||
}
|
||||
|
||||
if ((error = git_buf_joinpath(&path, repo->gitdir, "gitdir")) < 0)
|
||||
goto cleanup;
|
||||
/* A 'gitdir' file inside a git directory is currently
|
||||
* only used when the repository is a working tree. */
|
||||
if (git_path_exists(path.ptr))
|
||||
repo->is_worktree = 1;
|
||||
|
||||
/*
|
||||
* We'd like to have the config, but git doesn't particularly
|
||||
@ -731,6 +828,7 @@ int git_repository_open_ext(
|
||||
}
|
||||
|
||||
cleanup:
|
||||
git_buf_free(&path);
|
||||
git_buf_free(&parent);
|
||||
git_config_free(config);
|
||||
|
||||
@ -748,6 +846,36 @@ int git_repository_open(git_repository **repo_out, const char *path)
|
||||
repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL);
|
||||
}
|
||||
|
||||
int git_repository_open_from_worktree(git_repository **repo_out, git_worktree *wt)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
git_repository *repo = NULL;
|
||||
int len, err;
|
||||
|
||||
assert(repo_out && wt);
|
||||
|
||||
*repo_out = NULL;
|
||||
len = strlen(wt->gitlink_path);
|
||||
|
||||
if (len <= 4 || strcasecmp(wt->gitlink_path + len - 4, ".git")) {
|
||||
err = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((err = git_buf_set(&path, wt->gitlink_path, len - 4)) < 0)
|
||||
goto out;
|
||||
|
||||
if ((err = git_repository_open(&repo, path.ptr)) < 0)
|
||||
goto out;
|
||||
|
||||
*repo_out = repo;
|
||||
|
||||
out:
|
||||
git_buf_free(&path);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int git_repository_wrap_odb(git_repository **repo_out, git_odb *odb)
|
||||
{
|
||||
git_repository *repo;
|
||||
@ -773,7 +901,7 @@ int git_repository_discover(
|
||||
|
||||
git_buf_sanitize(out);
|
||||
|
||||
return find_repo(out, NULL, NULL, start_path, flags, ceiling_dirs);
|
||||
return find_repo(out, NULL, NULL, NULL, start_path, flags, ceiling_dirs);
|
||||
}
|
||||
|
||||
static int load_config(
|
||||
@ -793,8 +921,7 @@ static int load_config(
|
||||
if ((error = git_config_new(&cfg)) < 0)
|
||||
return error;
|
||||
|
||||
error = git_buf_joinpath(
|
||||
&config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO);
|
||||
error = git_repository_item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG);
|
||||
if (error < 0)
|
||||
goto on_error;
|
||||
|
||||
@ -928,7 +1055,8 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo)
|
||||
git_buf odb_path = GIT_BUF_INIT;
|
||||
git_odb *odb;
|
||||
|
||||
if ((error = git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR)) < 0)
|
||||
if ((error = git_repository_item_path(&odb_path, repo,
|
||||
GIT_REPOSITORY_ITEM_OBJECTS)) < 0)
|
||||
return error;
|
||||
|
||||
error = git_odb_open(&odb, odb_path.ptr);
|
||||
@ -1014,7 +1142,7 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo)
|
||||
git_buf index_path = GIT_BUF_INIT;
|
||||
git_index *index;
|
||||
|
||||
if ((error = git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE)) < 0)
|
||||
if ((error = git_buf_joinpath(&index_path, repo->gitdir, GIT_INDEX_FILE)) < 0)
|
||||
return error;
|
||||
|
||||
error = git_index_open(&index, index_path.ptr);
|
||||
@ -1130,13 +1258,13 @@ bool git_repository__reserved_names(
|
||||
prefixcmp = (error || ignorecase) ? git__prefixcmp_icase :
|
||||
git__prefixcmp;
|
||||
|
||||
if (repo->path_gitlink &&
|
||||
reserved_names_add8dot3(repo, repo->path_gitlink) < 0)
|
||||
if (repo->gitlink &&
|
||||
reserved_names_add8dot3(repo, repo->gitlink) < 0)
|
||||
goto on_error;
|
||||
|
||||
if (repo->path_repository &&
|
||||
prefixcmp(repo->path_repository, repo->workdir) == 0 &&
|
||||
reserved_names_add8dot3(repo, repo->path_repository) < 0)
|
||||
if (repo->gitdir &&
|
||||
prefixcmp(repo->gitdir, repo->workdir) == 0 &&
|
||||
reserved_names_add8dot3(repo, repo->gitdir) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
@ -1193,7 +1321,7 @@ static int check_repositoryformatversion(git_config *config)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int repo_init_create_head(const char *git_dir, const char *ref_name)
|
||||
int git_repository_create_head(const char *git_dir, const char *ref_name)
|
||||
{
|
||||
git_buf ref_path = GIT_BUF_INIT;
|
||||
git_filebuf ref = GIT_FILEBUF_INIT;
|
||||
@ -1856,7 +1984,8 @@ int git_repository_init_ext(
|
||||
git_repository_init_options *opts)
|
||||
{
|
||||
int error;
|
||||
git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT;
|
||||
git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT,
|
||||
common_path = GIT_BUF_INIT;
|
||||
const char *wd;
|
||||
|
||||
assert(out && given_repo && opts);
|
||||
@ -1868,7 +1997,7 @@ int git_repository_init_ext(
|
||||
goto cleanup;
|
||||
|
||||
wd = (opts->flags & GIT_REPOSITORY_INIT_BARE) ? NULL : git_buf_cstr(&wd_path);
|
||||
if (valid_repository_path(&repo_path)) {
|
||||
if (valid_repository_path(&repo_path, &common_path)) {
|
||||
|
||||
if ((opts->flags & GIT_REPOSITORY_INIT_NO_REINIT) != 0) {
|
||||
giterr_set(GITERR_REPOSITORY,
|
||||
@ -1889,7 +2018,7 @@ int git_repository_init_ext(
|
||||
repo_path.ptr, wd, opts)) &&
|
||||
!(error = repo_init_config(
|
||||
repo_path.ptr, wd, opts->flags, opts->mode)))
|
||||
error = repo_init_create_head(
|
||||
error = git_repository_create_head(
|
||||
repo_path.ptr, opts->initial_head);
|
||||
}
|
||||
if (error < 0)
|
||||
@ -1901,6 +2030,7 @@ int git_repository_init_ext(
|
||||
error = repo_init_create_origin(*out, opts->origin_url);
|
||||
|
||||
cleanup:
|
||||
git_buf_free(&common_path);
|
||||
git_buf_free(&repo_path);
|
||||
git_buf_free(&wd_path);
|
||||
|
||||
@ -1930,6 +2060,49 @@ int git_repository_head_detached(git_repository *repo)
|
||||
return exists;
|
||||
}
|
||||
|
||||
static int read_worktree_head(git_buf *out, git_repository *repo, const char *name)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
int err;
|
||||
|
||||
assert(out && repo && name);
|
||||
|
||||
git_buf_clear(out);
|
||||
|
||||
if ((err = git_buf_printf(&path, "%s/worktrees/%s/HEAD", repo->commondir, name)) < 0)
|
||||
goto out;
|
||||
if (!git_path_exists(path.ptr))
|
||||
{
|
||||
err = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((err = git_futils_readbuffer(out, path.ptr)) < 0)
|
||||
goto out;
|
||||
git_buf_rtrim(out);
|
||||
|
||||
out:
|
||||
git_buf_free(&path);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int git_repository_head_detached_for_worktree(git_repository *repo, const char *name)
|
||||
{
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
int ret;
|
||||
|
||||
assert(repo && name);
|
||||
|
||||
if (read_worktree_head(&buf, repo, name) < 0)
|
||||
return -1;
|
||||
|
||||
ret = git__strncmp(buf.ptr, GIT_SYMREF, strlen(GIT_SYMREF)) != 0;
|
||||
git_buf_free(&buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int git_repository_head(git_reference **head_out, git_repository *repo)
|
||||
{
|
||||
git_reference *head;
|
||||
@ -1949,6 +2122,48 @@ int git_repository_head(git_reference **head_out, git_repository *repo)
|
||||
return error == GIT_ENOTFOUND ? GIT_EUNBORNBRANCH : error;
|
||||
}
|
||||
|
||||
int git_repository_head_for_worktree(git_reference **out, git_repository *repo, const char *name)
|
||||
{
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
git_reference *head;
|
||||
int err;
|
||||
|
||||
assert(out && repo && name);
|
||||
|
||||
*out = NULL;
|
||||
|
||||
if (git_repository_head_detached_for_worktree(repo, name))
|
||||
return -1;
|
||||
if ((err = read_worktree_head(&buf, repo, name)) < 0)
|
||||
goto out;
|
||||
|
||||
/* We can only resolve symbolic references */
|
||||
if (git__strncmp(buf.ptr, GIT_SYMREF, strlen(GIT_SYMREF)))
|
||||
{
|
||||
err = -1;
|
||||
goto out;
|
||||
}
|
||||
git_buf_consume(&buf, buf.ptr + strlen(GIT_SYMREF));
|
||||
|
||||
if ((err = git_reference_lookup(&head, repo, buf.ptr)) < 0)
|
||||
goto out;
|
||||
if (git_reference_type(head) == GIT_REF_OID)
|
||||
{
|
||||
*out = head;
|
||||
err = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
err = git_reference_lookup_resolved(
|
||||
out, repo, git_reference_symbolic_target(head), -1);
|
||||
git_reference_free(head);
|
||||
|
||||
out:
|
||||
git_buf_free(&buf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int git_repository_head_unborn(git_repository *repo)
|
||||
{
|
||||
git_reference *ref = NULL;
|
||||
@ -2007,10 +2222,50 @@ int git_repository_is_empty(git_repository *repo)
|
||||
return is_empty;
|
||||
}
|
||||
|
||||
int git_repository_item_path(git_buf *out, git_repository *repo, git_repository_item_t item)
|
||||
{
|
||||
const char *parent;
|
||||
|
||||
switch (items[item].parent) {
|
||||
case GIT_REPOSITORY_ITEM_GITDIR:
|
||||
parent = git_repository_path(repo);
|
||||
break;
|
||||
case GIT_REPOSITORY_ITEM_WORKDIR:
|
||||
parent = git_repository_workdir(repo);
|
||||
break;
|
||||
case GIT_REPOSITORY_ITEM_COMMONDIR:
|
||||
parent = git_repository_commondir(repo);
|
||||
break;
|
||||
default:
|
||||
giterr_set(GITERR_INVALID, "Invalid item directory");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (parent == NULL) {
|
||||
giterr_set(GITERR_INVALID, "Path cannot exist in repository");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (git_buf_sets(out, parent) < 0)
|
||||
return -1;
|
||||
|
||||
if (items[item].name) {
|
||||
if (git_buf_joinpath(out, parent, items[item].name) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (items[item].directory) {
|
||||
if (git_path_to_dir(out) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *git_repository_path(git_repository *repo)
|
||||
{
|
||||
assert(repo);
|
||||
return repo->path_repository;
|
||||
return repo->gitdir;
|
||||
}
|
||||
|
||||
const char *git_repository_workdir(git_repository *repo)
|
||||
@ -2023,6 +2278,12 @@ const char *git_repository_workdir(git_repository *repo)
|
||||
return repo->workdir;
|
||||
}
|
||||
|
||||
const char *git_repository_commondir(git_repository *repo)
|
||||
{
|
||||
assert(repo);
|
||||
return repo->commondir;
|
||||
}
|
||||
|
||||
int git_repository_set_workdir(
|
||||
git_repository *repo, const char *workdir, int update_gitlink)
|
||||
{
|
||||
@ -2073,6 +2334,12 @@ int git_repository_is_bare(git_repository *repo)
|
||||
return repo->is_bare;
|
||||
}
|
||||
|
||||
int git_repository_is_worktree(git_repository *repo)
|
||||
{
|
||||
assert(repo);
|
||||
return repo->is_worktree;
|
||||
}
|
||||
|
||||
int git_repository_set_bare(git_repository *repo)
|
||||
{
|
||||
int error;
|
||||
@ -2127,7 +2394,7 @@ int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head
|
||||
|
||||
git_oid_fmt(orig_head_str, orig_head);
|
||||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_ORIG_HEAD_FILE)) == 0 &&
|
||||
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_ORIG_HEAD_FILE)) == 0 &&
|
||||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) == 0 &&
|
||||
(error = git_filebuf_printf(&file, "%.*s\n", GIT_OID_HEXSZ, orig_head_str)) == 0)
|
||||
error = git_filebuf_commit(&file);
|
||||
@ -2148,7 +2415,7 @@ int git_repository_message(git_buf *out, git_repository *repo)
|
||||
|
||||
git_buf_sanitize(out);
|
||||
|
||||
if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
|
||||
if (git_buf_joinpath(&path, repo->gitdir, GIT_MERGE_MSG_FILE) < 0)
|
||||
return -1;
|
||||
|
||||
if ((error = p_stat(git_buf_cstr(&path), &st)) < 0) {
|
||||
@ -2169,7 +2436,7 @@ int git_repository_message_remove(git_repository *repo)
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
int error;
|
||||
|
||||
if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
|
||||
if (git_buf_joinpath(&path, repo->gitdir, GIT_MERGE_MSG_FILE) < 0)
|
||||
return -1;
|
||||
|
||||
error = p_unlink(git_buf_cstr(&path));
|
||||
@ -2290,6 +2557,12 @@ int git_repository_set_head(
|
||||
if (error < 0 && error != GIT_ENOTFOUND)
|
||||
goto cleanup;
|
||||
|
||||
if (ref && current->type == GIT_REF_SYMBOLIC && git__strcmp(current->target.symbolic, ref->name) &&
|
||||
git_reference_is_branch(ref) && git_branch_is_checked_out(ref)) {
|
||||
error = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
if (git_reference_is_branch(ref)) {
|
||||
error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE,
|
||||
@ -2405,7 +2678,7 @@ int git_repository_state(git_repository *repo)
|
||||
|
||||
assert(repo);
|
||||
|
||||
if (git_buf_puts(&repo_path, repo->path_repository) < 0)
|
||||
if (git_buf_puts(&repo_path, repo->gitdir) < 0)
|
||||
return -1;
|
||||
|
||||
if (git_path_contains_file(&repo_path, GIT_REBASE_MERGE_INTERACTIVE_FILE))
|
||||
@ -2447,7 +2720,7 @@ int git_repository__cleanup_files(
|
||||
for (error = 0, i = 0; !error && i < files_len; ++i) {
|
||||
const char *path;
|
||||
|
||||
if (git_buf_joinpath(&buf, repo->path_repository, files[i]) < 0)
|
||||
if (git_buf_joinpath(&buf, repo->gitdir, files[i]) < 0)
|
||||
return -1;
|
||||
|
||||
path = git_buf_cstr(&buf);
|
||||
@ -2491,7 +2764,7 @@ int git_repository_is_shallow(git_repository *repo)
|
||||
struct stat st;
|
||||
int error;
|
||||
|
||||
if ((error = git_buf_joinpath(&path, repo->path_repository, "shallow")) < 0)
|
||||
if ((error = git_buf_joinpath(&path, repo->gitdir, "shallow")) < 0)
|
||||
return error;
|
||||
|
||||
error = git_path_lstat(path.ptr, &st);
|
||||
|
@ -126,8 +126,9 @@ struct git_repository {
|
||||
git_attr_cache *attrcache;
|
||||
git_diff_driver_registry *diff_drivers;
|
||||
|
||||
char *path_repository;
|
||||
char *path_gitlink;
|
||||
char *gitlink;
|
||||
char *gitdir;
|
||||
char *commondir;
|
||||
char *workdir;
|
||||
char *namespace;
|
||||
|
||||
@ -137,6 +138,7 @@ struct git_repository {
|
||||
git_array_t(git_buf) reserved_names;
|
||||
|
||||
unsigned is_bare:1;
|
||||
unsigned is_worktree:1;
|
||||
|
||||
unsigned int lru_counter;
|
||||
|
||||
@ -152,6 +154,7 @@ GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo)
|
||||
}
|
||||
|
||||
int git_repository_head_tree(git_tree **tree, git_repository *repo);
|
||||
int git_repository_create_head(const char *git_dir, const char *ref_name);
|
||||
|
||||
/*
|
||||
* Weak pointers to repository internals.
|
||||
|
@ -27,7 +27,7 @@ static int write_revert_head(
|
||||
git_buf file_path = GIT_BUF_INIT;
|
||||
int error = 0;
|
||||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_REVERT_HEAD_FILE)) >= 0 &&
|
||||
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_REVERT_HEAD_FILE)) >= 0 &&
|
||||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_REVERT_FILE_MODE)) >= 0 &&
|
||||
(error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0)
|
||||
error = git_filebuf_commit(&file);
|
||||
@ -49,7 +49,7 @@ static int write_merge_msg(
|
||||
git_buf file_path = GIT_BUF_INIT;
|
||||
int error = 0;
|
||||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
|
||||
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 ||
|
||||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_REVERT_FILE_MODE)) < 0 ||
|
||||
(error = git_filebuf_printf(&file, "Revert \"%s\"\n\nThis reverts commit %s.\n",
|
||||
commit_msgline, commit_oidstr)) < 0)
|
||||
|
@ -616,8 +616,10 @@ static int submodule_repo_init(
|
||||
* Old style: sub-repo goes directly into repo/<name>/.git/
|
||||
*/
|
||||
if (use_gitlink) {
|
||||
error = git_buf_join3(
|
||||
&repodir, '/', git_repository_path(parent_repo), "modules", path);
|
||||
error = git_repository_item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
error = git_buf_joinpath(&repodir, repodir.ptr, path);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
@ -1084,8 +1086,10 @@ static int submodule_repo_create(
|
||||
* <repo-dir>/modules/<name>/ with a gitlink in the
|
||||
* sub-repo workdir directory to that repository.
|
||||
*/
|
||||
error = git_buf_join3(
|
||||
&repodir, '/', git_repository_path(parent_repo), "modules", path);
|
||||
error = git_repository_item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
error = git_buf_joinpath(&repodir, repodir.ptr, path);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
|
@ -375,7 +375,8 @@ static int local_push(
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
if ((error = git_buf_joinpath(&odb_path, git_repository_path(remote_repo), "objects/pack")) < 0)
|
||||
if ((error = git_repository_item_path(&odb_path, remote_repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0
|
||||
|| (error = git_buf_joinpath(&odb_path, odb_path.ptr, "pack")) < 0)
|
||||
goto on_error;
|
||||
|
||||
error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs);
|
||||
|
432
src/worktree.c
Normal file
432
src/worktree.c
Normal file
@ -0,0 +1,432 @@
|
||||
/*
|
||||
* 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 "git2/branch.h"
|
||||
#include "git2/commit.h"
|
||||
#include "git2/worktree.h"
|
||||
|
||||
#include "repository.h"
|
||||
#include "worktree.h"
|
||||
|
||||
static bool is_worktree_dir(git_buf *dir)
|
||||
{
|
||||
return git_path_contains_file(dir, "commondir")
|
||||
&& git_path_contains_file(dir, "gitdir")
|
||||
&& git_path_contains_file(dir, "HEAD");
|
||||
}
|
||||
|
||||
int git_worktree_list(git_strarray *wts, git_repository *repo)
|
||||
{
|
||||
git_vector worktrees = GIT_VECTOR_INIT;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
char *worktree;
|
||||
unsigned i, len;
|
||||
int error;
|
||||
|
||||
assert(wts && repo);
|
||||
|
||||
wts->count = 0;
|
||||
wts->strings = NULL;
|
||||
|
||||
if ((error = git_buf_printf(&path, "%s/worktrees/", repo->commondir)) < 0)
|
||||
goto exit;
|
||||
if (!git_path_exists(path.ptr) || git_path_is_empty_dir(path.ptr))
|
||||
goto exit;
|
||||
if ((error = git_path_dirload(&worktrees, path.ptr, path.size, 0x0)) < 0)
|
||||
goto exit;
|
||||
|
||||
len = path.size;
|
||||
|
||||
git_vector_foreach(&worktrees, i, worktree) {
|
||||
git_buf_truncate(&path, len);
|
||||
git_buf_puts(&path, worktree);
|
||||
|
||||
if (!is_worktree_dir(&path)) {
|
||||
git_vector_remove(&worktrees, i);
|
||||
git__free(worktree);
|
||||
}
|
||||
}
|
||||
|
||||
wts->strings = (char **)git_vector_detach(&wts->count, NULL, &worktrees);
|
||||
|
||||
exit:
|
||||
git_buf_free(&path);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
char *git_worktree__read_link(const char *base, const char *file)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT;
|
||||
|
||||
assert(base && file);
|
||||
|
||||
if (git_buf_joinpath(&path, base, file) < 0)
|
||||
goto err;
|
||||
if (git_futils_readbuffer(&buf, path.ptr) < 0)
|
||||
goto err;
|
||||
git_buf_free(&path);
|
||||
|
||||
git_buf_rtrim(&buf);
|
||||
|
||||
if (!git_path_is_relative(buf.ptr))
|
||||
return git_buf_detach(&buf);
|
||||
|
||||
if (git_buf_sets(&path, base) < 0)
|
||||
goto err;
|
||||
if (git_path_apply_relative(&path, buf.ptr) < 0)
|
||||
goto err;
|
||||
git_buf_free(&buf);
|
||||
|
||||
return git_buf_detach(&path);
|
||||
|
||||
err:
|
||||
git_buf_free(&buf);
|
||||
git_buf_free(&path);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int write_wtfile(const char *base, const char *file, const git_buf *buf)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
int err;
|
||||
|
||||
assert(base && file && buf);
|
||||
|
||||
if ((err = git_buf_joinpath(&path, base, file)) < 0)
|
||||
goto out;
|
||||
|
||||
if ((err = git_futils_writebuffer(buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0)
|
||||
goto out;
|
||||
|
||||
out:
|
||||
git_buf_free(&path);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
git_worktree *wt = NULL;
|
||||
int error;
|
||||
|
||||
assert(repo && name);
|
||||
|
||||
*out = NULL;
|
||||
|
||||
if ((error = git_buf_printf(&path, "%s/worktrees/%s", repo->commondir, name)) < 0)
|
||||
goto out;
|
||||
|
||||
if (!is_worktree_dir(&path)) {
|
||||
error = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((wt = git__malloc(sizeof(struct git_repository))) == NULL) {
|
||||
error = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((wt->name = git__strdup(name)) == NULL
|
||||
|| (wt->commondir_path = git_worktree__read_link(path.ptr, "commondir")) == NULL
|
||||
|| (wt->gitlink_path = git_worktree__read_link(path.ptr, "gitdir")) == NULL
|
||||
|| (wt->parent_path = git__strdup(git_repository_path(repo))) == NULL) {
|
||||
error = -1;
|
||||
goto out;
|
||||
}
|
||||
wt->gitdir_path = git_buf_detach(&path);
|
||||
wt->locked = !!git_worktree_is_locked(NULL, wt);
|
||||
|
||||
(*out) = wt;
|
||||
|
||||
out:
|
||||
git_buf_free(&path);
|
||||
|
||||
if (error)
|
||||
git_worktree_free(wt);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
void git_worktree_free(git_worktree *wt)
|
||||
{
|
||||
if (!wt)
|
||||
return;
|
||||
|
||||
git__free(wt->commondir_path);
|
||||
git__free(wt->gitlink_path);
|
||||
git__free(wt->gitdir_path);
|
||||
git__free(wt->parent_path);
|
||||
git__free(wt->name);
|
||||
git__free(wt);
|
||||
}
|
||||
|
||||
int git_worktree_validate(const git_worktree *wt)
|
||||
{
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
int err = 0;
|
||||
|
||||
assert(wt);
|
||||
|
||||
git_buf_puts(&buf, wt->gitdir_path);
|
||||
if (!is_worktree_dir(&buf)) {
|
||||
giterr_set(GITERR_WORKTREE,
|
||||
"Worktree gitdir ('%s') is not valid",
|
||||
wt->gitlink_path);
|
||||
err = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!git_path_exists(wt->parent_path)) {
|
||||
giterr_set(GITERR_WORKTREE,
|
||||
"Worktree parent directory ('%s') does not exist ",
|
||||
wt->parent_path);
|
||||
err = -2;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!git_path_exists(wt->commondir_path)) {
|
||||
giterr_set(GITERR_WORKTREE,
|
||||
"Worktree common directory ('%s') does not exist ",
|
||||
wt->commondir_path);
|
||||
err = -3;
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
git_buf_free(&buf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT;
|
||||
git_reference *ref = NULL, *head = NULL;
|
||||
git_commit *commit = NULL;
|
||||
git_repository *wt = NULL;
|
||||
git_checkout_options coopts = GIT_CHECKOUT_OPTIONS_INIT;
|
||||
int err;
|
||||
|
||||
assert(out && repo && name && worktree);
|
||||
|
||||
*out = NULL;
|
||||
|
||||
/* Create worktree related files in commondir */
|
||||
if ((err = git_buf_joinpath(&path, repo->commondir, "worktrees")) < 0)
|
||||
goto out;
|
||||
if (!git_path_exists(path.ptr))
|
||||
if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
|
||||
goto out;
|
||||
if ((err = git_buf_joinpath(&path, path.ptr, name)) < 0)
|
||||
goto out;
|
||||
if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
|
||||
goto out;
|
||||
|
||||
/* Create worktree work dir */
|
||||
if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0)
|
||||
goto out;
|
||||
|
||||
/* Create worktree .git file */
|
||||
if ((err = git_buf_printf(&buf, "gitdir: %s\n", path.ptr)) < 0)
|
||||
goto out;
|
||||
if ((err = write_wtfile(worktree, ".git", &buf)) < 0)
|
||||
goto out;
|
||||
|
||||
/* Create commondir files */
|
||||
if ((err = git_buf_sets(&buf, repo->commondir)) < 0
|
||||
|| (err = git_buf_putc(&buf, '\n')) < 0
|
||||
|| (err = write_wtfile(path.ptr, "commondir", &buf)) < 0)
|
||||
goto out;
|
||||
if ((err = git_buf_joinpath(&buf, worktree, ".git")) < 0
|
||||
|| (err = git_buf_putc(&buf, '\n')) < 0
|
||||
|| (err = write_wtfile(path.ptr, "gitdir", &buf)) < 0)
|
||||
goto out;
|
||||
|
||||
/* Create new branch */
|
||||
if ((err = git_repository_head(&head, repo)) < 0)
|
||||
goto out;
|
||||
if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0)
|
||||
goto out;
|
||||
if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0)
|
||||
goto out;
|
||||
|
||||
/* Set worktree's HEAD */
|
||||
if ((err = git_repository_create_head(path.ptr, name)) < 0)
|
||||
goto out;
|
||||
if ((err = git_repository_open(&wt, worktree)) < 0)
|
||||
goto out;
|
||||
|
||||
/* Checkout worktree's HEAD */
|
||||
coopts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
if ((err = git_checkout_head(wt, &coopts)) < 0)
|
||||
goto out;
|
||||
|
||||
/* Load result */
|
||||
if ((err = git_worktree_lookup(out, repo, name)) < 0)
|
||||
goto out;
|
||||
|
||||
out:
|
||||
git_buf_free(&path);
|
||||
git_buf_free(&buf);
|
||||
git_reference_free(ref);
|
||||
git_reference_free(head);
|
||||
git_commit_free(commit);
|
||||
git_repository_free(wt);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int git_worktree_lock(git_worktree *wt, char *creason)
|
||||
{
|
||||
git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
|
||||
int err;
|
||||
|
||||
assert(wt);
|
||||
|
||||
if ((err = git_worktree_is_locked(NULL, wt)) < 0)
|
||||
goto out;
|
||||
|
||||
if ((err = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0)
|
||||
goto out;
|
||||
|
||||
if (creason)
|
||||
git_buf_attach_notowned(&buf, creason, strlen(creason));
|
||||
|
||||
if ((err = git_futils_writebuffer(&buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0)
|
||||
goto out;
|
||||
|
||||
wt->locked = 1;
|
||||
|
||||
out:
|
||||
git_buf_free(&path);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int git_worktree_unlock(git_worktree *wt)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
assert(wt);
|
||||
|
||||
if (!git_worktree_is_locked(NULL, wt))
|
||||
return 0;
|
||||
|
||||
if (git_buf_joinpath(&path, wt->gitdir_path, "locked") < 0)
|
||||
return -1;
|
||||
|
||||
if (p_unlink(path.ptr) != 0) {
|
||||
git_buf_free(&path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
wt->locked = 0;
|
||||
|
||||
git_buf_free(&path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_worktree_is_locked(git_buf *reason, const git_worktree *wt)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
int ret;
|
||||
|
||||
assert(wt);
|
||||
|
||||
if (reason)
|
||||
git_buf_clear(reason);
|
||||
|
||||
if ((ret = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0)
|
||||
goto out;
|
||||
if ((ret = git_path_exists(path.ptr)) && reason)
|
||||
git_futils_readbuffer(reason, path.ptr);
|
||||
|
||||
out:
|
||||
git_buf_free(&path);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int git_worktree_is_prunable(git_worktree *wt, unsigned flags)
|
||||
{
|
||||
git_buf reason = GIT_BUF_INIT;
|
||||
|
||||
if ((flags & GIT_WORKTREE_PRUNE_LOCKED) == 0 &&
|
||||
git_worktree_is_locked(&reason, wt))
|
||||
{
|
||||
if (!reason.size)
|
||||
git_buf_attach_notowned(&reason, "no reason given", 15);
|
||||
giterr_set(GITERR_WORKTREE, "Not pruning locked working tree: '%s'", reason.ptr);
|
||||
git_buf_free(&reason);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((flags & GIT_WORKTREE_PRUNE_VALID) == 0 &&
|
||||
git_worktree_validate(wt) == 0)
|
||||
{
|
||||
giterr_set(GITERR_WORKTREE, "Not pruning valid working tree");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int git_worktree_prune(git_worktree *wt, unsigned flags)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
char *wtpath;
|
||||
int err;
|
||||
|
||||
if (!git_worktree_is_prunable(wt, flags)) {
|
||||
err = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Delete gitdir in parent repository */
|
||||
if ((err = git_buf_printf(&path, "%s/worktrees/%s", wt->parent_path, wt->name)) < 0)
|
||||
goto out;
|
||||
if (!git_path_exists(path.ptr))
|
||||
{
|
||||
giterr_set(GITERR_WORKTREE, "Worktree gitdir '%s' does not exist", path.ptr);
|
||||
err = -1;
|
||||
goto out;
|
||||
}
|
||||
if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0)
|
||||
goto out;
|
||||
|
||||
/* Skip deletion of the actual working tree if it does
|
||||
* not exist or deletion was not requested */
|
||||
if ((flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 ||
|
||||
!git_path_exists(wt->gitlink_path))
|
||||
{
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((wtpath = git_path_dirname(wt->gitlink_path)) == NULL)
|
||||
goto out;
|
||||
git_buf_attach(&path, wtpath, 0);
|
||||
if (!git_path_exists(path.ptr))
|
||||
{
|
||||
giterr_set(GITERR_WORKTREE, "Working tree '%s' does not exist", path.ptr);
|
||||
err = -1;
|
||||
goto out;
|
||||
}
|
||||
if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0)
|
||||
goto out;
|
||||
|
||||
out:
|
||||
git_buf_free(&path);
|
||||
|
||||
return err;
|
||||
}
|
35
src/worktree.h
Normal file
35
src/worktree.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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_worktree_h__
|
||||
#define INCLUDE_worktree_h__
|
||||
|
||||
#include "git2/common.h"
|
||||
#include "git2/worktree.h"
|
||||
|
||||
struct git_worktree {
|
||||
/* Name of the working tree. This is the name of the
|
||||
* containing directory in the `$PARENT/.git/worktrees/`
|
||||
* directory. */
|
||||
char *name;
|
||||
|
||||
/* Path to the .git file in the working tree's repository */
|
||||
char *gitlink_path;
|
||||
/* Path to the .git directory inside the parent's
|
||||
* worktrees directory */
|
||||
char *gitdir_path;
|
||||
/* Path to the common directory contained in the parent
|
||||
* repository */
|
||||
char *commondir_path;
|
||||
/* Path to the parent's .git directory */
|
||||
char *parent_path;
|
||||
|
||||
int locked:1;
|
||||
};
|
||||
|
||||
char *git_worktree__read_link(const char *base, const char *file);
|
||||
|
||||
#endif
|
@ -613,9 +613,11 @@ void test_iterator_workdir__filesystem2(void)
|
||||
"heads/ident",
|
||||
"heads/long-file-name",
|
||||
"heads/master",
|
||||
"heads/merge-conflict",
|
||||
"heads/packed-test",
|
||||
"heads/subtrees",
|
||||
"heads/test",
|
||||
"heads/testrepo-worktree",
|
||||
"tags/e90810b",
|
||||
"tags/foo/bar",
|
||||
"tags/foo/foo/bar",
|
||||
@ -628,7 +630,7 @@ void test_iterator_workdir__filesystem2(void)
|
||||
|
||||
cl_git_pass(git_iterator_for_filesystem(
|
||||
&i, "testrepo/.git/refs", NULL));
|
||||
expect_iterator_items(i, 13, expect_base, 13, expect_base);
|
||||
expect_iterator_items(i, 15, expect_base, 15, expect_base);
|
||||
git_iterator_free(i);
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ void test_refs_list__all(void)
|
||||
/* We have exactly 12 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_equal_i((int)ref_list.count, 15);
|
||||
cl_assert_equal_i((int)ref_list.count, 17);
|
||||
|
||||
git_strarray_free(&ref_list);
|
||||
}
|
||||
@ -51,7 +51,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten
|
||||
"144344043ba4d4a405da03de3844aa829ae8be0e\n");
|
||||
|
||||
cl_git_pass(git_reference_list(&ref_list, g_repo));
|
||||
cl_assert_equal_i((int)ref_list.count, 15);
|
||||
cl_assert_equal_i((int)ref_list.count, 17);
|
||||
|
||||
git_strarray_free(&ref_list);
|
||||
}
|
||||
|
BIN
tests/resources/submodules-worktree-child/.gitted
Normal file
BIN
tests/resources/submodules-worktree-child/.gitted
Normal file
Binary file not shown.
BIN
tests/resources/submodules-worktree-child/README
Normal file
BIN
tests/resources/submodules-worktree-child/README
Normal file
Binary file not shown.
BIN
tests/resources/submodules-worktree-child/branch_file.txt
Normal file
BIN
tests/resources/submodules-worktree-child/branch_file.txt
Normal file
Binary file not shown.
BIN
tests/resources/submodules-worktree-child/new.txt
Normal file
BIN
tests/resources/submodules-worktree-child/new.txt
Normal file
Binary file not shown.
BIN
tests/resources/submodules-worktree-parent/.gitmodules
vendored
Normal file
BIN
tests/resources/submodules-worktree-parent/.gitmodules
vendored
Normal file
Binary file not shown.
BIN
tests/resources/submodules-worktree-parent/.gitted
Normal file
BIN
tests/resources/submodules-worktree-parent/.gitted
Normal file
Binary file not shown.
BIN
tests/resources/submodules-worktree-parent/deleted
Normal file
BIN
tests/resources/submodules-worktree-parent/deleted
Normal file
Binary file not shown.
BIN
tests/resources/submodules-worktree-parent/modified
Normal file
BIN
tests/resources/submodules-worktree-parent/modified
Normal file
Binary file not shown.
BIN
tests/resources/submodules-worktree-parent/unmodified
Normal file
BIN
tests/resources/submodules-worktree-parent/unmodified
Normal file
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tests/resources/testrepo-worktree/.gitted
Normal file
BIN
tests/resources/testrepo-worktree/.gitted
Normal file
Binary file not shown.
BIN
tests/resources/testrepo-worktree/README
Normal file
BIN
tests/resources/testrepo-worktree/README
Normal file
Binary file not shown.
BIN
tests/resources/testrepo-worktree/branch_file.txt
Normal file
BIN
tests/resources/testrepo-worktree/branch_file.txt
Normal file
Binary file not shown.
1
tests/resources/testrepo-worktree/link_to_new.txt
Symbolic link
1
tests/resources/testrepo-worktree/link_to_new.txt
Symbolic link
@ -0,0 +1 @@
|
||||
new.txt
|
BIN
tests/resources/testrepo-worktree/new.txt
Normal file
BIN
tests/resources/testrepo-worktree/new.txt
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tests/resources/testrepo/.gitted/refs/heads/merge-conflict
Normal file
BIN
tests/resources/testrepo/.gitted/refs/heads/merge-conflict
Normal file
Binary file not shown.
BIN
tests/resources/testrepo/.gitted/refs/heads/testrepo-worktree
Normal file
BIN
tests/resources/testrepo/.gitted/refs/heads/testrepo-worktree
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -177,7 +177,7 @@ void test_revwalk_basic__glob_heads_with_invalid(void)
|
||||
/* walking */;
|
||||
|
||||
/* git log --branches --oneline | wc -l => 16 */
|
||||
cl_assert_equal_i(18, i);
|
||||
cl_assert_equal_i(19, i);
|
||||
}
|
||||
|
||||
void test_revwalk_basic__push_head(void)
|
||||
|
45
tests/worktree/config.c
Normal file
45
tests/worktree/config.c
Normal file
@ -0,0 +1,45 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "worktree_helpers.h"
|
||||
|
||||
#define COMMON_REPO "testrepo"
|
||||
#define WORKTREE_REPO "testrepo-worktree"
|
||||
|
||||
static worktree_fixture fixture =
|
||||
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
|
||||
|
||||
void test_worktree_config__initialize(void)
|
||||
{
|
||||
setup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_config__cleanup(void)
|
||||
{
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_config__open(void)
|
||||
{
|
||||
git_config *cfg;
|
||||
|
||||
cl_git_pass(git_repository_config(&cfg, fixture.worktree));
|
||||
cl_assert(cfg != NULL);
|
||||
|
||||
git_config_free(cfg);
|
||||
}
|
||||
|
||||
void test_worktree_config__set(void)
|
||||
{
|
||||
git_config *cfg;
|
||||
int32_t val;
|
||||
|
||||
cl_git_pass(git_repository_config(&cfg, fixture.worktree));
|
||||
cl_git_pass(git_config_set_int32(cfg, "core.dummy", 5));
|
||||
git_config_free(cfg);
|
||||
|
||||
// reopen to verify configuration has been set in the
|
||||
// common dir
|
||||
cl_git_pass(git_repository_config(&cfg, fixture.repo));
|
||||
cl_git_pass(git_config_get_int32(&val, cfg, "core.dummy"));
|
||||
cl_assert_equal_i(val, 5);
|
||||
git_config_free(cfg);
|
||||
}
|
121
tests/worktree/merge.c
Normal file
121
tests/worktree/merge.c
Normal file
@ -0,0 +1,121 @@
|
||||
#include "clar_libgit2.h"
|
||||
|
||||
#include "worktree_helpers.h"
|
||||
#include "merge/merge_helpers.h"
|
||||
|
||||
#define COMMON_REPO "testrepo"
|
||||
#define WORKTREE_REPO "testrepo-worktree"
|
||||
|
||||
#define MASTER_BRANCH "refs/heads/master"
|
||||
#define CONFLICT_BRANCH "refs/heads/merge-conflict"
|
||||
|
||||
#define CONFLICT_BRANCH_FILE_TXT \
|
||||
"<<<<<<< HEAD\n" \
|
||||
"hi\n" \
|
||||
"bye!\n" \
|
||||
"=======\n" \
|
||||
"conflict\n" \
|
||||
">>>>>>> merge-conflict\n" \
|
||||
|
||||
static worktree_fixture fixture =
|
||||
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
|
||||
|
||||
static const char *merge_files[] = {
|
||||
GIT_MERGE_HEAD_FILE,
|
||||
GIT_ORIG_HEAD_FILE,
|
||||
GIT_MERGE_MODE_FILE,
|
||||
GIT_MERGE_MSG_FILE,
|
||||
};
|
||||
|
||||
void test_worktree_merge__initialize(void)
|
||||
{
|
||||
setup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_merge__cleanup(void)
|
||||
{
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_merge__merge_head(void)
|
||||
{
|
||||
git_reference *theirs_ref, *ref;
|
||||
git_annotated_commit *theirs;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&theirs_ref, fixture.worktree, CONFLICT_BRANCH));
|
||||
cl_git_pass(git_annotated_commit_from_ref(&theirs, fixture.worktree, theirs_ref));
|
||||
cl_git_pass(git_merge(fixture.worktree, (const git_annotated_commit **)&theirs, 1, NULL, NULL));
|
||||
|
||||
cl_git_pass(git_reference_lookup(&ref, fixture.worktree, GIT_MERGE_HEAD_FILE));
|
||||
|
||||
git_reference_free(ref);
|
||||
git_reference_free(theirs_ref);
|
||||
git_annotated_commit_free(theirs);
|
||||
}
|
||||
|
||||
void test_worktree_merge__merge_setup(void)
|
||||
{
|
||||
git_reference *ours_ref, *theirs_ref;
|
||||
git_annotated_commit *ours, *theirs;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
unsigned i;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&ours_ref, fixture.worktree, MASTER_BRANCH));
|
||||
cl_git_pass(git_annotated_commit_from_ref(&ours, fixture.worktree, ours_ref));
|
||||
|
||||
cl_git_pass(git_reference_lookup(&theirs_ref, fixture.worktree, CONFLICT_BRANCH));
|
||||
cl_git_pass(git_annotated_commit_from_ref(&theirs, fixture.worktree, theirs_ref));
|
||||
|
||||
cl_git_pass(git_merge__setup(fixture.worktree,
|
||||
ours, (const git_annotated_commit **)&theirs, 1));
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(merge_files); i++) {
|
||||
git_buf_clear(&path);
|
||||
cl_git_pass(git_buf_printf(&path, "%s/%s",
|
||||
fixture.worktree->gitdir, merge_files[i]));
|
||||
cl_assert(git_path_exists(path.ptr));
|
||||
}
|
||||
|
||||
git_buf_free(&path);
|
||||
git_reference_free(ours_ref);
|
||||
git_reference_free(theirs_ref);
|
||||
git_annotated_commit_free(ours);
|
||||
git_annotated_commit_free(theirs);
|
||||
}
|
||||
|
||||
void test_worktree_merge__merge_conflict(void)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT;
|
||||
git_reference *theirs_ref;
|
||||
git_annotated_commit *theirs;
|
||||
git_index *index;
|
||||
const git_index_entry *entry;
|
||||
size_t i, conflicts = 0;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&theirs_ref, fixture.worktree, CONFLICT_BRANCH));
|
||||
cl_git_pass(git_annotated_commit_from_ref(&theirs, fixture.worktree, theirs_ref));
|
||||
|
||||
cl_git_pass(git_merge(fixture.worktree,
|
||||
(const git_annotated_commit **)&theirs, 1, NULL, NULL));
|
||||
|
||||
cl_git_pass(git_repository_index(&index, fixture.worktree));
|
||||
for (i = 0; i < git_index_entrycount(index); i++) {
|
||||
cl_assert(entry = git_index_get_byindex(index, i));
|
||||
|
||||
if (git_index_entry_is_conflict(entry))
|
||||
conflicts++;
|
||||
}
|
||||
cl_assert_equal_sz(conflicts, 3);
|
||||
|
||||
git_reference_free(theirs_ref);
|
||||
git_annotated_commit_free(theirs);
|
||||
git_index_free(index);
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, fixture.worktree->workdir, "branch_file.txt"));
|
||||
cl_git_pass(git_futils_readbuffer(&buf, path.ptr));
|
||||
cl_assert_equal_s(buf.ptr, CONFLICT_BRANCH_FILE_TXT);
|
||||
|
||||
git_buf_free(&path);
|
||||
git_buf_free(&buf);
|
||||
}
|
||||
|
194
tests/worktree/open.c
Normal file
194
tests/worktree/open.c
Normal file
@ -0,0 +1,194 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "repository.h"
|
||||
#include "worktree_helpers.h"
|
||||
|
||||
#define WORKTREE_PARENT "submodules-worktree-parent"
|
||||
#define WORKTREE_CHILD "submodules-worktree-child"
|
||||
|
||||
#define COMMON_REPO "testrepo"
|
||||
#define WORKTREE_REPO "testrepo-worktree"
|
||||
|
||||
static void assert_worktree_valid(git_repository *wt, const char *parentdir, const char *wtdir)
|
||||
{
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
cl_assert(wt->is_worktree);
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, clar_sandbox_path(), wtdir));
|
||||
cl_git_pass(git_path_prettify(&path, path.ptr, NULL));
|
||||
cl_git_pass(git_path_to_dir(&path));
|
||||
cl_assert_equal_s(wt->workdir, path.ptr);
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, path.ptr, ".git"));
|
||||
cl_git_pass(git_path_prettify(&path, path.ptr, NULL));
|
||||
cl_assert_equal_s(wt->gitlink, path.ptr);
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, clar_sandbox_path(), parentdir));
|
||||
cl_git_pass(git_buf_joinpath(&path, path.ptr, ".git"));
|
||||
cl_git_pass(git_buf_joinpath(&path, path.ptr, "worktrees"));
|
||||
cl_git_pass(git_buf_joinpath(&path, path.ptr, wtdir));
|
||||
cl_git_pass(git_path_prettify(&path, path.ptr, NULL));
|
||||
cl_git_pass(git_path_to_dir(&path));
|
||||
cl_assert_equal_s(wt->gitdir, path.ptr);
|
||||
|
||||
git_buf_free(&path);
|
||||
}
|
||||
|
||||
void test_worktree_open__repository(void)
|
||||
{
|
||||
worktree_fixture fixture =
|
||||
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
|
||||
setup_fixture_worktree(&fixture);
|
||||
|
||||
assert_worktree_valid(fixture.worktree, COMMON_REPO, WORKTREE_REPO);
|
||||
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_open__repository_through_workdir(void)
|
||||
{
|
||||
worktree_fixture fixture =
|
||||
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
|
||||
git_repository *wt;
|
||||
|
||||
setup_fixture_worktree(&fixture);
|
||||
|
||||
cl_git_pass(git_repository_open(&wt, WORKTREE_REPO));
|
||||
assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO);
|
||||
|
||||
git_repository_free(wt);
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_open__repository_through_gitlink(void)
|
||||
{
|
||||
worktree_fixture fixture =
|
||||
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
|
||||
git_repository *wt;
|
||||
|
||||
setup_fixture_worktree(&fixture);
|
||||
|
||||
cl_git_pass(git_repository_open(&wt, WORKTREE_REPO "/.git"));
|
||||
assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO);
|
||||
|
||||
git_repository_free(wt);
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_open__repository_through_gitdir(void)
|
||||
{
|
||||
worktree_fixture fixture =
|
||||
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
|
||||
git_buf gitdir_path = GIT_BUF_INIT;
|
||||
git_repository *wt;
|
||||
|
||||
setup_fixture_worktree(&fixture);
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&gitdir_path, COMMON_REPO, ".git"));
|
||||
cl_git_pass(git_buf_joinpath(&gitdir_path, gitdir_path.ptr, "worktrees"));
|
||||
cl_git_pass(git_buf_joinpath(&gitdir_path, gitdir_path.ptr, "testrepo-worktree"));
|
||||
|
||||
cl_git_pass(git_repository_open(&wt, gitdir_path.ptr));
|
||||
assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO);
|
||||
|
||||
git_buf_free(&gitdir_path);
|
||||
git_repository_free(wt);
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_open__open_discovered_worktree(void)
|
||||
{
|
||||
worktree_fixture fixture =
|
||||
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
git_repository *repo;
|
||||
|
||||
setup_fixture_worktree(&fixture);
|
||||
|
||||
cl_git_pass(git_repository_discover(&path,
|
||||
git_repository_workdir(fixture.worktree), false, NULL));
|
||||
cl_git_pass(git_repository_open(&repo, path.ptr));
|
||||
cl_assert_equal_s(git_repository_workdir(fixture.worktree),
|
||||
git_repository_workdir(repo));
|
||||
|
||||
git_buf_free(&path);
|
||||
git_repository_free(repo);
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_open__repository_with_nonexistent_parent(void)
|
||||
{
|
||||
git_repository *repo;
|
||||
|
||||
cl_fixture_sandbox(WORKTREE_REPO);
|
||||
cl_git_pass(p_chdir(WORKTREE_REPO));
|
||||
cl_git_pass(cl_rename(".gitted", ".git"));
|
||||
cl_git_pass(p_chdir(".."));
|
||||
|
||||
cl_git_fail(git_repository_open(&repo, WORKTREE_REPO));
|
||||
|
||||
cl_fixture_cleanup(WORKTREE_REPO);
|
||||
}
|
||||
|
||||
void test_worktree_open__submodule_worktree_parent(void)
|
||||
{
|
||||
worktree_fixture fixture =
|
||||
WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT);
|
||||
setup_fixture_worktree(&fixture);
|
||||
|
||||
cl_assert(git_repository_path(fixture.worktree) != NULL);
|
||||
cl_assert(git_repository_workdir(fixture.worktree) != NULL);
|
||||
|
||||
cl_assert(!fixture.repo->is_worktree);
|
||||
cl_assert(fixture.worktree->is_worktree);
|
||||
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_open__submodule_worktree_child(void)
|
||||
{
|
||||
worktree_fixture parent_fixture =
|
||||
WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT);
|
||||
worktree_fixture child_fixture =
|
||||
WORKTREE_FIXTURE_INIT(NULL, WORKTREE_CHILD);
|
||||
|
||||
setup_fixture_worktree(&parent_fixture);
|
||||
cl_git_pass(p_rename(
|
||||
"submodules/testrepo/.gitted",
|
||||
"submodules/testrepo/.git"));
|
||||
setup_fixture_worktree(&child_fixture);
|
||||
|
||||
cl_assert(!parent_fixture.repo->is_worktree);
|
||||
cl_assert(parent_fixture.worktree->is_worktree);
|
||||
cl_assert(child_fixture.worktree->is_worktree);
|
||||
|
||||
cleanup_fixture_worktree(&child_fixture);
|
||||
cleanup_fixture_worktree(&parent_fixture);
|
||||
}
|
||||
|
||||
void test_worktree_open__open_discovered_submodule_worktree(void)
|
||||
{
|
||||
worktree_fixture parent_fixture =
|
||||
WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT);
|
||||
worktree_fixture child_fixture =
|
||||
WORKTREE_FIXTURE_INIT(NULL, WORKTREE_CHILD);
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
git_repository *repo;
|
||||
|
||||
setup_fixture_worktree(&parent_fixture);
|
||||
cl_git_pass(p_rename(
|
||||
"submodules/testrepo/.gitted",
|
||||
"submodules/testrepo/.git"));
|
||||
setup_fixture_worktree(&child_fixture);
|
||||
|
||||
cl_git_pass(git_repository_discover(&path,
|
||||
git_repository_workdir(child_fixture.worktree), false, NULL));
|
||||
cl_git_pass(git_repository_open(&repo, path.ptr));
|
||||
cl_assert_equal_s(git_repository_workdir(child_fixture.worktree),
|
||||
git_repository_workdir(repo));
|
||||
|
||||
git_buf_free(&path);
|
||||
git_repository_free(repo);
|
||||
cleanup_fixture_worktree(&child_fixture);
|
||||
cleanup_fixture_worktree(&parent_fixture);
|
||||
}
|
65
tests/worktree/reflog.c
Normal file
65
tests/worktree/reflog.c
Normal file
@ -0,0 +1,65 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "worktree_helpers.h"
|
||||
|
||||
#include "reflog.h"
|
||||
|
||||
#define COMMON_REPO "testrepo"
|
||||
#define WORKTREE_REPO "testrepo-worktree"
|
||||
|
||||
#define REFLOG "refs/heads/testrepo-worktree"
|
||||
#define REFLOG_MESSAGE "reflog message"
|
||||
|
||||
static worktree_fixture fixture =
|
||||
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
|
||||
|
||||
void test_worktree_reflog__initialize(void)
|
||||
{
|
||||
setup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_reflog__cleanup(void)
|
||||
{
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_reflog__read(void)
|
||||
{
|
||||
git_reflog *reflog;
|
||||
const git_reflog_entry *entry;
|
||||
|
||||
cl_git_pass(git_reflog_read(&reflog, fixture.worktree, REFLOG));
|
||||
cl_assert_equal_i(git_reflog_entrycount(reflog), 1);
|
||||
|
||||
entry = git_reflog_entry_byindex(reflog, 0);
|
||||
cl_assert(entry != NULL);
|
||||
cl_assert_equal_s(git_reflog_entry_message(entry), "branch: Created from HEAD");
|
||||
|
||||
git_reflog_free(reflog);
|
||||
}
|
||||
|
||||
void test_worktree_reflog__append_then_read(void)
|
||||
{
|
||||
git_reflog *reflog, *parent_reflog;
|
||||
const git_reflog_entry *entry;
|
||||
git_reference *head;
|
||||
git_signature *sig;
|
||||
const git_oid *oid;
|
||||
|
||||
cl_git_pass(git_repository_head(&head, fixture.worktree));
|
||||
cl_assert((oid = git_reference_target(head)) != NULL);
|
||||
cl_git_pass(git_signature_now(&sig, "foo", "foo@bar"));
|
||||
|
||||
cl_git_pass(git_reflog_read(&reflog, fixture.worktree, REFLOG));
|
||||
cl_git_pass(git_reflog_append(reflog, oid, sig, REFLOG_MESSAGE));
|
||||
git_reflog_write(reflog);
|
||||
|
||||
cl_git_pass(git_reflog_read(&parent_reflog, fixture.repo, REFLOG));
|
||||
entry = git_reflog_entry_byindex(parent_reflog, 0);
|
||||
cl_assert(git_oid_cmp(oid, &entry->oid_old) == 0);
|
||||
cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0);
|
||||
|
||||
git_reference_free(head);
|
||||
git_signature_free(sig);
|
||||
git_reflog_free(reflog);
|
||||
git_reflog_free(parent_reflog);
|
||||
}
|
130
tests/worktree/refs.c
Normal file
130
tests/worktree/refs.c
Normal file
@ -0,0 +1,130 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "worktree.h"
|
||||
#include "worktree_helpers.h"
|
||||
|
||||
#define COMMON_REPO "testrepo"
|
||||
#define WORKTREE_REPO "testrepo-worktree"
|
||||
|
||||
static worktree_fixture fixture =
|
||||
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
|
||||
|
||||
void test_worktree_refs__initialize(void)
|
||||
{
|
||||
setup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_refs__cleanup(void)
|
||||
{
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_refs__list(void)
|
||||
{
|
||||
git_strarray refs, wtrefs;
|
||||
unsigned i, j;
|
||||
int error = 0;
|
||||
|
||||
cl_git_pass(git_reference_list(&refs, fixture.repo));
|
||||
cl_git_pass(git_reference_list(&wtrefs, fixture.worktree));
|
||||
|
||||
if (refs.count != wtrefs.count)
|
||||
{
|
||||
error = GIT_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
for (i = 0; i < refs.count; i++)
|
||||
{
|
||||
int found = 0;
|
||||
|
||||
for (j = 0; j < wtrefs.count; j++)
|
||||
{
|
||||
if (!strcmp(refs.strings[i], wtrefs.strings[j]))
|
||||
{
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
error = GIT_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
git_strarray_free(&refs);
|
||||
git_strarray_free(&wtrefs);
|
||||
cl_git_pass(error);
|
||||
}
|
||||
|
||||
void test_worktree_refs__read_head(void)
|
||||
{
|
||||
git_reference *head;
|
||||
|
||||
cl_git_pass(git_repository_head(&head, fixture.worktree));
|
||||
|
||||
git_reference_free(head);
|
||||
}
|
||||
|
||||
void test_worktree_refs__set_head_fails_when_worktree_wants_linked_repos_HEAD(void)
|
||||
{
|
||||
git_reference *head;
|
||||
|
||||
cl_git_pass(git_repository_head(&head, fixture.repo));
|
||||
cl_git_fail(git_repository_set_head(fixture.worktree, git_reference_name(head)));
|
||||
|
||||
git_reference_free(head);
|
||||
}
|
||||
|
||||
void test_worktree_refs__set_head_fails_when_main_repo_wants_worktree_head(void)
|
||||
{
|
||||
git_reference *head;
|
||||
|
||||
cl_git_pass(git_repository_head(&head, fixture.worktree));
|
||||
cl_git_fail(git_repository_set_head(fixture.repo, git_reference_name(head)));
|
||||
|
||||
git_reference_free(head);
|
||||
}
|
||||
|
||||
void test_worktree_refs__set_head_works_for_current_HEAD(void)
|
||||
{
|
||||
git_reference *head;
|
||||
|
||||
cl_git_pass(git_repository_head(&head, fixture.repo));
|
||||
cl_git_pass(git_repository_set_head(fixture.repo, git_reference_name(head)));
|
||||
|
||||
git_reference_free(head);
|
||||
}
|
||||
|
||||
void test_worktree_refs__set_head_fails_when_already_checked_out(void)
|
||||
{
|
||||
cl_git_fail(git_repository_set_head(fixture.repo, "refs/heads/testrepo-worktree"));
|
||||
}
|
||||
|
||||
void test_worktree_refs__delete_fails_for_checked_out_branch(void)
|
||||
{
|
||||
git_reference *branch;
|
||||
|
||||
cl_git_pass(git_branch_lookup(&branch, fixture.repo,
|
||||
"testrepo-worktree", GIT_BRANCH_LOCAL));
|
||||
cl_git_fail(git_branch_delete(branch));
|
||||
|
||||
git_reference_free(branch);
|
||||
}
|
||||
|
||||
void test_worktree_refs__delete_succeeds_after_pruning_worktree(void)
|
||||
{
|
||||
git_reference *branch;
|
||||
git_worktree *worktree;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&worktree, fixture.repo, fixture.worktreename));
|
||||
cl_git_pass(git_worktree_prune(worktree, GIT_WORKTREE_PRUNE_VALID));
|
||||
git_worktree_free(worktree);
|
||||
|
||||
cl_git_pass(git_branch_lookup(&branch, fixture.repo,
|
||||
"testrepo-worktree", GIT_BRANCH_LOCAL));
|
||||
cl_git_pass(git_branch_delete(branch));
|
||||
git_reference_free(branch);
|
||||
}
|
63
tests/worktree/repository.c
Normal file
63
tests/worktree/repository.c
Normal file
@ -0,0 +1,63 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "worktree_helpers.h"
|
||||
#include "submodule/submodule_helpers.h"
|
||||
|
||||
#include "repository.h"
|
||||
|
||||
#define COMMON_REPO "testrepo"
|
||||
#define WORKTREE_REPO "testrepo-worktree"
|
||||
|
||||
static worktree_fixture fixture =
|
||||
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
|
||||
|
||||
void test_worktree_repository__initialize(void)
|
||||
{
|
||||
setup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_repository__cleanup(void)
|
||||
{
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_repository__head(void)
|
||||
{
|
||||
git_reference *ref, *head;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&ref, fixture.repo, "refs/heads/testrepo-worktree"));
|
||||
cl_git_pass(git_repository_head_for_worktree(&head, fixture.repo, "testrepo-worktree"));
|
||||
cl_assert(git_reference_cmp(ref, head) == 0);
|
||||
|
||||
git_reference_free(ref);
|
||||
git_reference_free(head);
|
||||
}
|
||||
|
||||
void test_worktree_repository__head_fails_for_invalid_worktree(void)
|
||||
{
|
||||
git_reference *head = NULL;
|
||||
|
||||
cl_git_fail(git_repository_head_for_worktree(&head, fixture.repo, "invalid"));
|
||||
cl_assert(head == NULL);
|
||||
}
|
||||
|
||||
void test_worktree_repository__head_detached(void)
|
||||
{
|
||||
git_reference *ref, *head;
|
||||
|
||||
cl_git_pass(git_reference_lookup(&ref, fixture.repo, "refs/heads/testrepo-worktree"));
|
||||
cl_git_pass(git_repository_set_head_detached(fixture.worktree, &ref->target.oid));
|
||||
|
||||
cl_assert(git_repository_head_detached(fixture.worktree));
|
||||
cl_assert(git_repository_head_detached_for_worktree(fixture.repo, "testrepo-worktree"));
|
||||
cl_git_fail(git_repository_head_for_worktree(&head, fixture.repo, "testrepo-worktree"));
|
||||
|
||||
git_reference_free(ref);
|
||||
}
|
||||
|
||||
void test_worktree_repository__head_detached_fails_for_invalid_worktree(void)
|
||||
{
|
||||
git_reference *head = NULL;
|
||||
|
||||
cl_git_fail(git_repository_head_detached_for_worktree(fixture.repo, "invalid"));
|
||||
cl_assert(head == NULL);
|
||||
}
|
485
tests/worktree/worktree.c
Normal file
485
tests/worktree/worktree.c
Normal file
@ -0,0 +1,485 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "worktree_helpers.h"
|
||||
#include "submodule/submodule_helpers.h"
|
||||
|
||||
#include "checkout.h"
|
||||
#include "repository.h"
|
||||
#include "worktree.h"
|
||||
|
||||
#define COMMON_REPO "testrepo"
|
||||
#define WORKTREE_REPO "testrepo-worktree"
|
||||
|
||||
static worktree_fixture fixture =
|
||||
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
|
||||
|
||||
void test_worktree_worktree__initialize(void)
|
||||
{
|
||||
setup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__cleanup(void)
|
||||
{
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__list(void)
|
||||
{
|
||||
git_strarray wts;
|
||||
|
||||
cl_git_pass(git_worktree_list(&wts, fixture.repo));
|
||||
cl_assert_equal_i(wts.count, 1);
|
||||
cl_assert_equal_s(wts.strings[0], "testrepo-worktree");
|
||||
|
||||
git_strarray_free(&wts);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__list_with_invalid_worktree_dirs(void)
|
||||
{
|
||||
const char *filesets[3][2] = {
|
||||
{ "gitdir", "commondir" },
|
||||
{ "gitdir", "HEAD" },
|
||||
{ "HEAD", "commondir" },
|
||||
};
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
git_strarray wts;
|
||||
unsigned i, j, len;
|
||||
|
||||
cl_git_pass(git_buf_printf(&path, "%s/worktrees/invalid",
|
||||
fixture.repo->commondir));
|
||||
cl_git_pass(p_mkdir(path.ptr, 0755));
|
||||
|
||||
len = path.size;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(filesets); i++) {
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(filesets[i]); j++) {
|
||||
git_buf_truncate(&path, len);
|
||||
cl_git_pass(git_buf_joinpath(&path, path.ptr, filesets[i][j]));
|
||||
cl_git_pass(p_close(p_creat(path.ptr, 0644)));
|
||||
}
|
||||
|
||||
cl_git_pass(git_worktree_list(&wts, fixture.worktree));
|
||||
cl_assert_equal_i(wts.count, 1);
|
||||
cl_assert_equal_s(wts.strings[0], "testrepo-worktree");
|
||||
git_strarray_free(&wts);
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(filesets[i]); j++) {
|
||||
git_buf_truncate(&path, len);
|
||||
cl_git_pass(git_buf_joinpath(&path, path.ptr, filesets[i][j]));
|
||||
p_unlink(path.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
git_buf_free(&path);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__list_in_worktree_repo(void)
|
||||
{
|
||||
git_strarray wts;
|
||||
|
||||
cl_git_pass(git_worktree_list(&wts, fixture.worktree));
|
||||
cl_assert_equal_i(wts.count, 1);
|
||||
cl_assert_equal_s(wts.strings[0], "testrepo-worktree");
|
||||
|
||||
git_strarray_free(&wts);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__list_bare(void)
|
||||
{
|
||||
git_repository *repo;
|
||||
git_strarray wts;
|
||||
|
||||
repo = cl_git_sandbox_init("testrepo.git");
|
||||
cl_git_pass(git_worktree_list(&wts, repo));
|
||||
cl_assert_equal_i(wts.count, 0);
|
||||
|
||||
git_repository_free(repo);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__list_without_worktrees(void)
|
||||
{
|
||||
git_repository *repo;
|
||||
git_strarray wts;
|
||||
|
||||
repo = cl_git_sandbox_init("testrepo2");
|
||||
cl_git_pass(git_worktree_list(&wts, repo));
|
||||
cl_assert_equal_i(wts.count, 0);
|
||||
|
||||
git_repository_free(repo);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__lookup(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
git_buf gitdir_path = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
|
||||
git_buf_printf(&gitdir_path, "%s/worktrees/%s", fixture.repo->commondir, "testrepo-worktree");
|
||||
|
||||
cl_assert_equal_s(wt->gitdir_path, gitdir_path.ptr);
|
||||
cl_assert_equal_s(wt->parent_path, fixture.repo->gitdir);
|
||||
cl_assert_equal_s(wt->gitlink_path, fixture.worktree->gitlink);
|
||||
cl_assert_equal_s(wt->commondir_path, fixture.repo->commondir);
|
||||
|
||||
git_buf_free(&gitdir_path);
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__lookup_nonexistent_worktree(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
|
||||
cl_git_fail(git_worktree_lookup(&wt, fixture.repo, "nonexistent"));
|
||||
cl_assert_equal_p(wt, NULL);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__open(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
git_repository *repo;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
|
||||
cl_git_pass(git_repository_open_from_worktree(&repo, wt));
|
||||
cl_assert_equal_s(git_repository_workdir(repo),
|
||||
git_repository_workdir(fixture.worktree));
|
||||
|
||||
git_repository_free(repo);
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__open_invalid_commondir(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
git_repository *repo;
|
||||
git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_buf_sets(&buf, "/path/to/nonexistent/commondir"));
|
||||
cl_git_pass(git_buf_printf(&path,
|
||||
"%s/worktrees/testrepo-worktree/commondir",
|
||||
fixture.repo->commondir));
|
||||
cl_git_pass(git_futils_writebuffer(&buf, path.ptr, O_RDWR, 0644));
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
cl_git_fail(git_repository_open_from_worktree(&repo, wt));
|
||||
|
||||
git_buf_free(&buf);
|
||||
git_buf_free(&path);
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__open_invalid_gitdir(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
git_repository *repo;
|
||||
git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_buf_sets(&buf, "/path/to/nonexistent/gitdir"));
|
||||
cl_git_pass(git_buf_printf(&path,
|
||||
"%s/worktrees/testrepo-worktree/gitdir",
|
||||
fixture.repo->commondir));
|
||||
cl_git_pass(git_futils_writebuffer(&buf, path.ptr, O_RDWR, 0644));
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
cl_git_fail(git_repository_open_from_worktree(&repo, wt));
|
||||
|
||||
git_buf_free(&buf);
|
||||
git_buf_free(&path);
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__open_invalid_parent(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
git_repository *repo;
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_buf_sets(&buf, "/path/to/nonexistent/gitdir"));
|
||||
cl_git_pass(git_futils_writebuffer(&buf,
|
||||
fixture.worktree->gitlink, O_RDWR, 0644));
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
cl_git_fail(git_repository_open_from_worktree(&repo, wt));
|
||||
|
||||
git_buf_free(&buf);
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__init(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
git_repository *repo;
|
||||
git_reference *branch;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new"));
|
||||
cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr));
|
||||
|
||||
/* Open and verify created repo */
|
||||
cl_git_pass(git_repository_open(&repo, path.ptr));
|
||||
cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-new/") == 0);
|
||||
cl_git_pass(git_branch_lookup(&branch, repo, "worktree-new", GIT_BRANCH_LOCAL));
|
||||
|
||||
git_buf_free(&path);
|
||||
git_worktree_free(wt);
|
||||
git_reference_free(branch);
|
||||
git_repository_free(repo);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__init_existing_branch(void)
|
||||
{
|
||||
git_reference *head, *branch;
|
||||
git_commit *commit;
|
||||
git_worktree *wt;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_repository_head(&head, fixture.repo));
|
||||
cl_git_pass(git_commit_lookup(&commit, fixture.repo, &head->target.oid));
|
||||
cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-new", commit, false));
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new"));
|
||||
cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr));
|
||||
|
||||
git_buf_free(&path);
|
||||
git_commit_free(commit);
|
||||
git_reference_free(head);
|
||||
git_reference_free(branch);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__init_existing_worktree(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new"));
|
||||
cl_git_fail(git_worktree_add(&wt, fixture.repo, "testrepo-worktree", path.ptr));
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
cl_assert_equal_s(wt->gitlink_path, fixture.worktree->gitlink);
|
||||
|
||||
git_buf_free(&path);
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__init_existing_path(void)
|
||||
{
|
||||
const char *wtfiles[] = { "HEAD", "commondir", "gitdir", "index" };
|
||||
git_worktree *wt;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
unsigned i;
|
||||
|
||||
/* Delete files to verify they have not been created by
|
||||
* the init call */
|
||||
for (i = 0; i < ARRAY_SIZE(wtfiles); i++) {
|
||||
cl_git_pass(git_buf_joinpath(&path,
|
||||
fixture.worktree->gitdir, wtfiles[i]));
|
||||
cl_git_pass(p_unlink(path.ptr));
|
||||
}
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../testrepo-worktree"));
|
||||
cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr));
|
||||
|
||||
/* Verify files have not been re-created */
|
||||
for (i = 0; i < ARRAY_SIZE(wtfiles); i++) {
|
||||
cl_git_pass(git_buf_joinpath(&path,
|
||||
fixture.worktree->gitdir, wtfiles[i]));
|
||||
cl_assert(!git_path_exists(path.ptr));
|
||||
}
|
||||
|
||||
git_buf_free(&path);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__init_submodule(void)
|
||||
{
|
||||
git_repository *repo, *sm, *wt;
|
||||
git_worktree *worktree;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
|
||||
cleanup_fixture_worktree(&fixture);
|
||||
repo = setup_fixture_submod2();
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, repo->workdir, "sm_unchanged"));
|
||||
cl_git_pass(git_repository_open(&sm, path.ptr));
|
||||
cl_git_pass(git_buf_joinpath(&path, repo->workdir, "../worktree/"));
|
||||
cl_git_pass(git_worktree_add(&worktree, sm, "repo-worktree", path.ptr));
|
||||
cl_git_pass(git_repository_open_from_worktree(&wt, worktree));
|
||||
|
||||
cl_assert_equal_s(path.ptr, wt->workdir);
|
||||
cl_assert_equal_s(sm->commondir, wt->commondir);
|
||||
|
||||
cl_git_pass(git_buf_joinpath(&path, sm->gitdir, "worktrees/repo-worktree/"));
|
||||
cl_assert_equal_s(path.ptr, wt->gitdir);
|
||||
|
||||
git_buf_free(&path);
|
||||
git_worktree_free(worktree);
|
||||
git_repository_free(sm);
|
||||
git_repository_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__validate(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
cl_git_pass(git_worktree_validate(wt));
|
||||
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__validate_invalid_commondir(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
git__free(wt->commondir_path);
|
||||
wt->commondir_path = "/path/to/invalid/commondir";
|
||||
|
||||
cl_git_fail(git_worktree_validate(wt));
|
||||
|
||||
wt->commondir_path = NULL;
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__validate_invalid_gitdir(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
git__free(wt->gitdir_path);
|
||||
wt->gitdir_path = "/path/to/invalid/gitdir";
|
||||
cl_git_fail(git_worktree_validate(wt));
|
||||
|
||||
wt->gitdir_path = NULL;
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__validate_invalid_parent(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
git__free(wt->parent_path);
|
||||
wt->parent_path = "/path/to/invalid/parent";
|
||||
cl_git_fail(git_worktree_validate(wt));
|
||||
|
||||
wt->parent_path = NULL;
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__lock_with_reason(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
git_buf reason = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
|
||||
cl_assert(!git_worktree_is_locked(NULL, wt));
|
||||
cl_git_pass(git_worktree_lock(wt, "because"));
|
||||
cl_assert(git_worktree_is_locked(&reason, wt) > 0);
|
||||
cl_assert_equal_s(reason.ptr, "because");
|
||||
cl_assert(wt->locked);
|
||||
|
||||
git_buf_free(&reason);
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__lock_without_reason(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
git_buf reason = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
|
||||
cl_assert(!git_worktree_is_locked(NULL, wt));
|
||||
cl_git_pass(git_worktree_lock(wt, NULL));
|
||||
cl_assert(git_worktree_is_locked(&reason, wt) > 0);
|
||||
cl_assert_equal_i(reason.size, 0);
|
||||
cl_assert(wt->locked);
|
||||
|
||||
git_buf_free(&reason);
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__unlock_unlocked_worktree(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
cl_assert(!git_worktree_is_locked(NULL, wt));
|
||||
cl_assert(git_worktree_unlock(wt) == 0);
|
||||
cl_assert(!wt->locked);
|
||||
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__unlock_locked_worktree(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
cl_git_pass(git_worktree_lock(wt, NULL));
|
||||
cl_assert(git_worktree_is_locked(NULL, wt));
|
||||
cl_git_pass(git_worktree_unlock(wt));
|
||||
cl_assert(!wt->locked);
|
||||
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__prune_valid(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
git_repository *repo;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
|
||||
|
||||
/* Assert the repository is not valid anymore */
|
||||
cl_git_fail(git_repository_open_from_worktree(&repo, wt));
|
||||
|
||||
git_worktree_free(wt);
|
||||
git_repository_free(repo);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__prune_locked(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
git_repository *repo;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
cl_git_pass(git_worktree_lock(wt, NULL));
|
||||
cl_git_fail(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
|
||||
cl_git_fail(git_worktree_prune(wt, ~GIT_WORKTREE_PRUNE_LOCKED));
|
||||
|
||||
/* Assert the repository is still valid */
|
||||
cl_git_pass(git_repository_open_from_worktree(&repo, wt));
|
||||
|
||||
git_worktree_free(wt);
|
||||
git_repository_free(repo);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__prune_gitdir(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
|
||||
|
||||
cl_assert(!git_path_exists(wt->gitdir_path));
|
||||
cl_assert(git_path_exists(wt->gitlink_path));
|
||||
|
||||
git_worktree_free(wt);
|
||||
}
|
||||
|
||||
void test_worktree_worktree__prune_both(void)
|
||||
{
|
||||
git_worktree *wt;
|
||||
|
||||
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
||||
cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_WORKING_TREE | GIT_WORKTREE_PRUNE_VALID));
|
||||
|
||||
cl_assert(!git_path_exists(wt->gitdir_path));
|
||||
cl_assert(!git_path_exists(wt->gitlink_path));
|
||||
|
||||
git_worktree_free(wt);
|
||||
}
|
30
tests/worktree/worktree_helpers.c
Normal file
30
tests/worktree/worktree_helpers.c
Normal file
@ -0,0 +1,30 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "worktree_helpers.h"
|
||||
|
||||
void cleanup_fixture_worktree(worktree_fixture *fixture)
|
||||
{
|
||||
if (!fixture)
|
||||
return;
|
||||
|
||||
if (fixture->repo) {
|
||||
git_repository_free(fixture->repo);
|
||||
fixture->repo = NULL;
|
||||
}
|
||||
if (fixture->worktree) {
|
||||
git_repository_free(fixture->worktree);
|
||||
fixture->worktree = NULL;
|
||||
}
|
||||
|
||||
if (fixture->reponame)
|
||||
cl_fixture_cleanup(fixture->reponame);
|
||||
if (fixture->worktreename)
|
||||
cl_fixture_cleanup(fixture->worktreename);
|
||||
}
|
||||
|
||||
void setup_fixture_worktree(worktree_fixture *fixture)
|
||||
{
|
||||
if (fixture->reponame)
|
||||
fixture->repo = cl_git_sandbox_init(fixture->reponame);
|
||||
if (fixture->worktreename)
|
||||
fixture->worktree = cl_git_sandbox_init(fixture->worktreename);
|
||||
}
|
11
tests/worktree/worktree_helpers.h
Normal file
11
tests/worktree/worktree_helpers.h
Normal file
@ -0,0 +1,11 @@
|
||||
typedef struct {
|
||||
const char *reponame;
|
||||
const char *worktreename;
|
||||
git_repository *repo;
|
||||
git_repository *worktree;
|
||||
} worktree_fixture;
|
||||
|
||||
#define WORKTREE_FIXTURE_INIT(repo, worktree) { (repo), (worktree), NULL, NULL }
|
||||
|
||||
void cleanup_fixture_worktree(worktree_fixture *fixture);
|
||||
void setup_fixture_worktree(worktree_fixture *fixture);
|
Loading…
Reference in New Issue
Block a user