Commit 4f9f8e0d by Edward Thomson Committed by GitHub

Merge pull request #3436 from pks-t/libgit2-worktree

Worktree implementation
parents 43275f51 1ba242c9
...@@ -246,6 +246,18 @@ GIT_EXTERN(int) git_branch_is_head( ...@@ -246,6 +246,18 @@ GIT_EXTERN(int) git_branch_is_head(
const git_reference *branch); 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. * Return the name of remote that the remote tracking branch belongs to.
* *
* @param out Pointer to the user-allocated git_buf which will be filled with the name of the remote. * @param out Pointer to the user-allocated git_buf which will be filled with the name of the remote.
......
...@@ -100,6 +100,7 @@ typedef enum { ...@@ -100,6 +100,7 @@ typedef enum {
GITERR_REBASE, GITERR_REBASE,
GITERR_FILESYSTEM, GITERR_FILESYSTEM,
GITERR_PATCH, GITERR_PATCH,
GITERR_WORKTREE
} git_error_t; } git_error_t;
/** /**
......
...@@ -35,6 +35,17 @@ GIT_BEGIN_DECL ...@@ -35,6 +35,17 @@ GIT_BEGIN_DECL
* @return 0 or an error code * @return 0 or an error code
*/ */
GIT_EXTERN(int) git_repository_open(git_repository **out, const char *path); 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 * Create a "fake" repository to wrap an object database
...@@ -335,6 +346,17 @@ GIT_EXTERN(int) git_repository_init_ext( ...@@ -335,6 +346,17 @@ GIT_EXTERN(int) git_repository_init_ext(
GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo); 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 * Check if a repository's HEAD is detached
* *
* A repository's HEAD is detached when it points directly to a commit * A repository's HEAD is detached when it points directly to a commit
...@@ -346,6 +368,20 @@ GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo); ...@@ -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); 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 * Check if the current branch is unborn
* *
...@@ -371,6 +407,42 @@ GIT_EXTERN(int) git_repository_head_unborn(git_repository *repo); ...@@ -371,6 +407,42 @@ GIT_EXTERN(int) git_repository_head_unborn(git_repository *repo);
GIT_EXTERN(int) git_repository_is_empty(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 * Get the path of this repository
* *
* This is the path of the `.git` folder for normal repositories, * This is the path of the `.git` folder for normal repositories,
...@@ -393,6 +465,17 @@ GIT_EXTERN(const char *) git_repository_path(git_repository *repo); ...@@ -393,6 +465,17 @@ GIT_EXTERN(const char *) git_repository_path(git_repository *repo);
GIT_EXTERN(const char *) git_repository_workdir(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 * Set the path to the working directory for this repository
* *
* The working directory doesn't need to be the same one * The working directory doesn't need to be the same one
...@@ -421,6 +504,14 @@ GIT_EXTERN(int) git_repository_set_workdir( ...@@ -421,6 +504,14 @@ GIT_EXTERN(int) git_repository_set_workdir(
GIT_EXTERN(int) git_repository_is_bare(git_repository *repo); 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. * Get the configuration file for this repository.
* *
* If a configuration file has not been set, the default * If a configuration file has not been set, the default
......
...@@ -104,6 +104,9 @@ typedef struct git_refdb_backend git_refdb_backend; ...@@ -104,6 +104,9 @@ typedef struct git_refdb_backend git_refdb_backend;
*/ */
typedef struct git_repository git_repository; 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 */ /** Representation of a generic object in a repository */
typedef struct git_object git_object; typedef struct git_object git_object;
......
/*
* 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
...@@ -292,7 +292,7 @@ static int attr_setup(git_repository *repo, git_attr_session *attr_session) ...@@ -292,7 +292,7 @@ static int attr_setup(git_repository *repo, git_attr_session *attr_session)
int error = 0; int error = 0;
const char *workdir = git_repository_workdir(repo); const char *workdir = git_repository_workdir(repo);
git_index *idx = NULL; git_index *idx = NULL;
git_buf sys = GIT_BUF_INIT; git_buf path = GIT_BUF_INIT;
if (attr_session && attr_session->init_setup) if (attr_session && attr_session->init_setup)
return 0; return 0;
...@@ -304,40 +304,45 @@ static int attr_setup(git_repository *repo, git_attr_session *attr_session) ...@@ -304,40 +304,45 @@ static int attr_setup(git_repository *repo, git_attr_session *attr_session)
* definitions will be available for later file parsing * 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) if (error == 0)
error = preload_attr_file( 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) if (error != GIT_ENOTFOUND)
return error; goto out;
git_buf_free(&sys);
if ((error = preload_attr_file( if ((error = preload_attr_file(
repo, attr_session, GIT_ATTR_FILE__FROM_FILE, repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0) 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( if ((error = preload_attr_file(
repo, attr_session, GIT_ATTR_FILE__FROM_FILE, repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
git_repository_path(repo), GIT_ATTR_FILE_INREPO)) < 0) path.ptr, GIT_ATTR_FILE_INREPO)) < 0)
return error; goto out;
if (workdir != NULL && if (workdir != NULL &&
(error = preload_attr_file( (error = preload_attr_file(
repo, attr_session, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0) 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 || if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
(error = preload_attr_file( (error = preload_attr_file(
repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0) repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0)
return error; goto out;
if (attr_session) if (attr_session)
attr_session->init_setup = 1; attr_session->init_setup = 1;
out:
git_buf_free(&path);
return error; return error;
} }
...@@ -472,7 +477,7 @@ static int collect_attr_files( ...@@ -472,7 +477,7 @@ static int collect_attr_files(
git_vector *files) git_vector *files)
{ {
int error = 0; 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); const char *workdir = git_repository_workdir(repo);
attr_walk_up_info info = { NULL }; attr_walk_up_info info = { NULL };
...@@ -494,9 +499,13 @@ static int collect_attr_files( ...@@ -494,9 +499,13 @@ static int collect_attr_files(
* - $GIT_PREFIX/etc/gitattributes * - $GIT_PREFIX/etc/gitattributes
*/ */
error = git_repository_item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO);
if (error < 0)
goto cleanup;
error = push_attr_file( error = push_attr_file(
repo, attr_session, files, GIT_ATTR_FILE__FROM_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) if (error < 0)
goto cleanup; goto cleanup;
...@@ -538,6 +547,7 @@ static int collect_attr_files( ...@@ -538,6 +547,7 @@ static int collect_attr_files(
cleanup: cleanup:
if (error < 0) if (error < 0)
release_attr_files(files); release_attr_files(files);
git_buf_free(&attrfile);
git_buf_free(&dir); git_buf_free(&dir);
return error; return error;
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
#include "fileops.h" #include "fileops.h"
#define GIT_ATTR_FILE ".gitattributes" #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_SYSTEM "gitattributes"
#define GIT_ATTR_FILE_XDG "attributes" #define GIT_ATTR_FILE_XDG "attributes"
......
...@@ -326,8 +326,8 @@ int git_blob_create_fromstream(git_writestream **out, git_repository *repo, cons ...@@ -326,8 +326,8 @@ int git_blob_create_fromstream(git_writestream **out, git_repository *repo, cons
stream->parent.close = blob_writestream_close; stream->parent.close = blob_writestream_close;
stream->parent.free = blob_writestream_free; stream->parent.free = blob_writestream_free;
if ((error = git_buf_joinpath(&path, if ((error = git_repository_item_path(&path, repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0
git_repository_path(repo), GIT_OBJECTS_DIR "streamed")) < 0) || (error = git_buf_joinpath(&path, path.ptr, "streamed")) < 0)
goto cleanup; goto cleanup;
if ((error = git_filebuf_open_withsize(&stream->fbuf, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY, if ((error = git_filebuf_open_withsize(&stream->fbuf, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY,
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "refs.h" #include "refs.h"
#include "remote.h" #include "remote.h"
#include "annotated_commit.h" #include "annotated_commit.h"
#include "worktree.h"
#include "git2/branch.h" #include "git2/branch.h"
...@@ -126,6 +127,62 @@ int git_branch_create_from_annotated( ...@@ -126,6 +127,62 @@ int git_branch_create_from_annotated(
repository, branch_name, commit->commit, commit->description, force); 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 git_branch_delete(git_reference *branch)
{ {
int is_head; int is_head;
...@@ -149,6 +206,12 @@ int git_branch_delete(git_reference *branch) ...@@ -149,6 +206,12 @@ int git_branch_delete(git_reference *branch)
return -1; 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", if (git_buf_join(&config_section, '.', "branch",
git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
goto on_error; goto on_error;
......
...@@ -28,7 +28,7 @@ static int write_cherrypick_head( ...@@ -28,7 +28,7 @@ static int write_cherrypick_head(
git_buf file_path = GIT_BUF_INIT; git_buf file_path = GIT_BUF_INIT;
int error = 0; 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_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_printf(&file, "%s\n", commit_oidstr)) >= 0)
error = git_filebuf_commit(&file); error = git_filebuf_commit(&file);
...@@ -49,7 +49,7 @@ static int write_merge_msg( ...@@ -49,7 +49,7 @@ static int write_merge_msg(
git_buf file_path = GIT_BUF_INIT; git_buf file_path = GIT_BUF_INIT;
int error = 0; 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_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) < 0 ||
(error = git_filebuf_printf(&file, "%s", commit_msg)) < 0) (error = git_filebuf_printf(&file, "%s", commit_msg)) < 0)
goto cleanup; goto cleanup;
......
...@@ -513,9 +513,8 @@ static int clone_local_into(git_repository *repo, git_remote *remote, const git_ ...@@ -513,9 +513,8 @@ static int clone_local_into(git_repository *repo, git_remote *remote, const git_
return error; return error;
} }
git_buf_joinpath(&src_odb, git_repository_path(src), GIT_OBJECTS_DIR); if (git_repository_item_path(&src_odb, src, GIT_REPOSITORY_ITEM_OBJECTS) < 0
git_buf_joinpath(&dst_odb, git_repository_path(repo), GIT_OBJECTS_DIR); || git_repository_item_path(&dst_odb, repo, GIT_REPOSITORY_ITEM_OBJECTS) < 0) {
if (git_buf_oom(&src_odb) || git_buf_oom(&dst_odb)) {
error = -1; error = -1;
goto cleanup; goto cleanup;
} }
......
...@@ -115,7 +115,7 @@ int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs) ...@@ -115,7 +115,7 @@ int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs)
assert(repo && 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; return -1;
if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE) < 0) { 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, ...@@ -249,7 +249,7 @@ int git_repository_fetchhead_foreach(git_repository *repo,
assert(repo && cb); 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; return -1;
if ((error = git_futils_readbuffer(&file, git_buf_cstr(&path))) < 0) if ((error = git_futils_readbuffer(&file, git_buf_cstr(&path))) < 0)
......
...@@ -277,6 +277,7 @@ int git_ignore__for_path( ...@@ -277,6 +277,7 @@ int git_ignore__for_path(
{ {
int error = 0; int error = 0;
const char *workdir = git_repository_workdir(repo); const char *workdir = git_repository_workdir(repo);
git_buf infopath = GIT_BUF_INIT;
assert(repo && ignores && path); assert(repo && ignores && path);
...@@ -322,10 +323,14 @@ int git_ignore__for_path( ...@@ -322,10 +323,14 @@ int git_ignore__for_path(
goto cleanup; goto cleanup;
} }
if ((error = git_repository_item_path(&infopath,
repo, GIT_REPOSITORY_ITEM_INFO)) < 0)
goto cleanup;
/* load .git/info/exclude */ /* load .git/info/exclude */
error = push_ignore_file( error = push_ignore_file(
ignores, &ignores->ign_global, ignores, &ignores->ign_global,
git_repository_path(repo), GIT_IGNORE_FILE_INREPO); infopath.ptr, GIT_IGNORE_FILE_INREPO);
if (error < 0) if (error < 0)
goto cleanup; goto cleanup;
...@@ -336,6 +341,7 @@ int git_ignore__for_path( ...@@ -336,6 +341,7 @@ int git_ignore__for_path(
git_repository_attr_cache(repo)->cfg_excl_file); git_repository_attr_cache(repo)->cfg_excl_file);
cleanup: cleanup:
git_buf_free(&infopath);
if (error < 0) if (error < 0)
git_ignore__free(ignores); git_ignore__free(ignores);
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
#include "attr_file.h" #include "attr_file.h"
#define GIT_IGNORE_FILE ".gitignore" #define GIT_IGNORE_FILE ".gitignore"
#define GIT_IGNORE_FILE_INREPO "info/exclude" #define GIT_IGNORE_FILE_INREPO "exclude"
#define GIT_IGNORE_FILE_XDG "ignore" #define GIT_IGNORE_FILE_XDG "ignore"
/* The git_ignores structure maintains three sets of ignores: /* The git_ignores structure maintains three sets of ignores:
......
...@@ -562,7 +562,7 @@ int git_repository_mergehead_foreach( ...@@ -562,7 +562,7 @@ int git_repository_mergehead_foreach(
assert(repo && cb); 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) GIT_MERGE_HEAD_FILE)) < 0)
return error; return error;
...@@ -2277,7 +2277,7 @@ static int write_merge_head( ...@@ -2277,7 +2277,7 @@ static int write_merge_head(
assert(repo && heads); 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) (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0)
goto cleanup; goto cleanup;
...@@ -2305,7 +2305,7 @@ static int write_merge_mode(git_repository *repo) ...@@ -2305,7 +2305,7 @@ static int write_merge_mode(git_repository *repo)
assert(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) (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0)
goto cleanup; goto cleanup;
...@@ -2536,7 +2536,7 @@ static int write_merge_msg( ...@@ -2536,7 +2536,7 @@ static int write_merge_msg(
for (i = 0; i < heads_len; i++) for (i = 0; i < heads_len; i++)
entries[i].merge_head = heads[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_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0 ||
(error = git_filebuf_write(&file, "Merge ", 6)) < 0) (error = git_filebuf_write(&file, "Merge ", 6)) < 0)
goto cleanup; goto cleanup;
...@@ -2914,7 +2914,7 @@ int git_merge__append_conflicts_to_merge_msg( ...@@ -2914,7 +2914,7 @@ int git_merge__append_conflicts_to_merge_msg(
if (!git_index_has_conflicts(index)) if (!git_index_has_conflicts(index))
return 0; 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) (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_APPEND, GIT_MERGE_FILE_MODE)) < 0)
goto cleanup; goto cleanup;
......
...@@ -92,7 +92,7 @@ static int rebase_state_type( ...@@ -92,7 +92,7 @@ static int rebase_state_type(
git_buf path = GIT_BUF_INIT; git_buf path = GIT_BUF_INIT;
git_rebase_type_t type = GIT_REBASE_TYPE_NONE; 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; return -1;
if (git_path_isdir(git_buf_cstr(&path))) { if (git_path_isdir(git_buf_cstr(&path))) {
...@@ -101,7 +101,7 @@ static int rebase_state_type( ...@@ -101,7 +101,7 @@ static int rebase_state_type(
} }
git_buf_clear(&path); 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; return -1;
if (git_path_isdir(git_buf_cstr(&path))) { if (git_path_isdir(git_buf_cstr(&path))) {
...@@ -624,7 +624,7 @@ static int rebase_init_merge( ...@@ -624,7 +624,7 @@ static int rebase_init_merge(
GIT_UNUSED(upstream); 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; goto done;
rebase->state_path = git_buf_detach(&state_path); rebase->state_path = git_buf_detach(&state_path);
......
...@@ -55,7 +55,10 @@ typedef struct refdb_fs_backend { ...@@ -55,7 +55,10 @@ typedef struct refdb_fs_backend {
git_refdb_backend parent; git_refdb_backend parent;
git_repository *repo; git_repository *repo;
char *path; /* path to git directory */
char *gitpath;
/* path to common objects' directory */
char *commonpath;
git_sortedcache *refcache; git_sortedcache *refcache;
int peeling_mode; int peeling_mode;
...@@ -77,7 +80,7 @@ static int packed_reload(refdb_fs_backend *backend) ...@@ -77,7 +80,7 @@ static int packed_reload(refdb_fs_backend *backend)
git_buf packedrefs = GIT_BUF_INIT; git_buf packedrefs = GIT_BUF_INIT;
char *scan, *eof, *eol; char *scan, *eof, *eol;
if (!backend->path) if (!backend->gitpath)
return 0; return 0;
error = git_sortedcache_lockandload(backend->refcache, &packedrefs); error = git_sortedcache_lockandload(backend->refcache, &packedrefs);
...@@ -238,7 +241,7 @@ static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name) ...@@ -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 /* if we fail to load the loose reference, assume someone changed
* the filesystem under us and skip it... * 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(); giterr_clear();
goto done; goto done;
} }
...@@ -287,7 +290,7 @@ static int _dirent_loose_load(void *payload, git_buf *full_path) ...@@ -287,7 +290,7 @@ static int _dirent_loose_load(void *payload, git_buf *full_path)
return error; 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); return loose_lookup_to_packfile(backend, file_path);
} }
...@@ -303,7 +306,7 @@ static int packed_loadloose(refdb_fs_backend *backend) ...@@ -303,7 +306,7 @@ static int packed_loadloose(refdb_fs_backend *backend)
int error; int error;
git_buf refs_path = GIT_BUF_INIT; 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; return -1;
/* /*
...@@ -331,7 +334,7 @@ static int refdb_fs_backend__exists( ...@@ -331,7 +334,7 @@ static int refdb_fs_backend__exists(
assert(backend); assert(backend);
if ((error = packed_reload(backend)) < 0 || 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; return error;
*exists = git_path_isfile(ref_path.ptr) || *exists = git_path_isfile(ref_path.ptr) ||
...@@ -362,6 +365,14 @@ static const char *loose_parse_symbolic(git_buf *file_content) ...@@ -362,6 +365,14 @@ static const char *loose_parse_symbolic(git_buf *file_content)
return refname_start; 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( static int loose_lookup(
git_reference **out, git_reference **out,
refdb_fs_backend *backend, refdb_fs_backend *backend,
...@@ -369,11 +380,17 @@ static int loose_lookup( ...@@ -369,11 +380,17 @@ static int loose_lookup(
{ {
git_buf ref_file = GIT_BUF_INIT; git_buf ref_file = GIT_BUF_INIT;
int error = 0; int error = 0;
const char *ref_dir;
if (out) if (out)
*out = NULL; *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 */; /* cannot read loose ref file - gah */;
else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) { else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) {
const char *target; const char *target;
...@@ -484,12 +501,12 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) ...@@ -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; git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT;
const git_index_entry *entry = NULL; 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; return 0;
fsit_opts.flags = backend->iterator_flags; 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) { (error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) {
git_buf_free(&path); git_buf_free(&path);
return error; return error;
...@@ -729,10 +746,10 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char * ...@@ -729,10 +746,10 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *
/* Remove a possibly existing empty directory hierarchy /* Remove a possibly existing empty directory hierarchy
* which name would collide with the reference name * 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; return error;
if (git_buf_joinpath(&ref_path, backend->path, name) < 0) if (git_buf_joinpath(&ref_path, backend->gitpath, name) < 0)
return -1; return -1;
error = git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE); 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( ...@@ -1283,7 +1300,7 @@ static int refdb_fs_backend__delete_tail(
} }
/* If a loose reference exists, remove it from the filesystem */ /* 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; return -1;
...@@ -1408,20 +1425,23 @@ static void refdb_fs_backend__free(git_refdb_backend *_backend) ...@@ -1408,20 +1425,23 @@ static void refdb_fs_backend__free(git_refdb_backend *_backend)
assert(backend); assert(backend);
git_sortedcache_free(backend->refcache); git_sortedcache_free(backend->refcache);
git__free(backend->path); git__free(backend->gitpath);
git__free(backend->commonpath);
git__free(backend); 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; char *parts, *start, *end;
/* Not all repositories have a path */ /* Not all repositories have a gitpath */
if (repo->path_repository == NULL) if (repo->gitdir == NULL)
return 0;
if (repo->commondir == NULL)
return 0; return 0;
/* Load the path to the repo first */ /* 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 the repo is not namespaced, nothing else to do */
if (repo->namespace == NULL) if (repo->namespace == NULL)
...@@ -1438,19 +1458,19 @@ static int setup_namespace(git_buf *path, git_repository *repo) ...@@ -1438,19 +1458,19 @@ static int setup_namespace(git_buf *path, git_repository *repo)
* refs under refs/namespaces/foo/refs/namespaces/bar/ * refs under refs/namespaces/foo/refs/namespaces/bar/
*/ */
while ((start = git__strsep(&end, "/")) != NULL) { 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); git__free(parts);
/* Make sure that the folder with the namespace exists */ /* 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) 0777, GIT_MKDIR_PATH, NULL) < 0)
return -1; return -1;
/* Return root of the namespaced path, i.e. without the trailing '/refs' */ /* Return root of the namespaced gitpath, i.e. without the trailing '/refs' */
git_buf_rtruncate_at_char(path, '/'); git_buf_rtruncate_at_char(gitpath, '/');
return 0; return 0;
} }
...@@ -1562,7 +1582,7 @@ static int create_new_reflog_file(const char *filepath) ...@@ -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) 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) 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_ ...@@ -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) &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0)
return error; 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; return -1;
if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0) if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0)
...@@ -1948,7 +1968,7 @@ int git_refdb_backend_fs( ...@@ -1948,7 +1968,7 @@ int git_refdb_backend_fs(
git_repository *repository) git_repository *repository)
{ {
int t = 0; int t = 0;
git_buf path = GIT_BUF_INIT; git_buf gitpath = GIT_BUF_INIT;
refdb_fs_backend *backend; refdb_fs_backend *backend;
backend = git__calloc(1, sizeof(refdb_fs_backend)); backend = git__calloc(1, sizeof(refdb_fs_backend));
...@@ -1956,18 +1976,20 @@ int git_refdb_backend_fs( ...@@ -1956,18 +1976,20 @@ int git_refdb_backend_fs(
backend->repo = repository; backend->repo = repository;
if (setup_namespace(&path, repository) < 0) if (setup_namespace(&gitpath, repository) < 0)
goto fail; 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( git_sortedcache_new(
&backend->refcache, offsetof(struct packref, name), &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; goto fail;
git_buf_free(&path); git_buf_free(&gitpath);
if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) { if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) {
backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE; backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE;
...@@ -1999,8 +2021,9 @@ int git_refdb_backend_fs( ...@@ -1999,8 +2021,9 @@ int git_refdb_backend_fs(
return 0; return 0;
fail: fail:
git_buf_free(&path); git_buf_free(&gitpath);
git__free(backend->path); git__free(backend->gitpath);
git__free(backend->commonpath);
git__free(backend); git__free(backend);
return -1; return -1;
} }
...@@ -126,8 +126,9 @@ struct git_repository { ...@@ -126,8 +126,9 @@ struct git_repository {
git_attr_cache *attrcache; git_attr_cache *attrcache;
git_diff_driver_registry *diff_drivers; git_diff_driver_registry *diff_drivers;
char *path_repository; char *gitlink;
char *path_gitlink; char *gitdir;
char *commondir;
char *workdir; char *workdir;
char *namespace; char *namespace;
...@@ -137,6 +138,7 @@ struct git_repository { ...@@ -137,6 +138,7 @@ struct git_repository {
git_array_t(git_buf) reserved_names; git_array_t(git_buf) reserved_names;
unsigned is_bare:1; unsigned is_bare:1;
unsigned is_worktree:1;
unsigned int lru_counter; unsigned int lru_counter;
...@@ -152,6 +154,7 @@ GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) ...@@ -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_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. * Weak pointers to repository internals.
......
...@@ -27,7 +27,7 @@ static int write_revert_head( ...@@ -27,7 +27,7 @@ static int write_revert_head(
git_buf file_path = GIT_BUF_INIT; git_buf file_path = GIT_BUF_INIT;
int error = 0; 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_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_printf(&file, "%s\n", commit_oidstr)) >= 0)
error = git_filebuf_commit(&file); error = git_filebuf_commit(&file);
...@@ -49,7 +49,7 @@ static int write_merge_msg( ...@@ -49,7 +49,7 @@ static int write_merge_msg(
git_buf file_path = GIT_BUF_INIT; git_buf file_path = GIT_BUF_INIT;
int error = 0; 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_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", (error = git_filebuf_printf(&file, "Revert \"%s\"\n\nThis reverts commit %s.\n",
commit_msgline, commit_oidstr)) < 0) commit_msgline, commit_oidstr)) < 0)
......
...@@ -616,8 +616,10 @@ static int submodule_repo_init( ...@@ -616,8 +616,10 @@ static int submodule_repo_init(
* Old style: sub-repo goes directly into repo/<name>/.git/ * Old style: sub-repo goes directly into repo/<name>/.git/
*/ */
if (use_gitlink) { if (use_gitlink) {
error = git_buf_join3( error = git_repository_item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES);
&repodir, '/', git_repository_path(parent_repo), "modules", path); if (error < 0)
goto cleanup;
error = git_buf_joinpath(&repodir, repodir.ptr, path);
if (error < 0) if (error < 0)
goto cleanup; goto cleanup;
...@@ -1084,8 +1086,10 @@ static int submodule_repo_create( ...@@ -1084,8 +1086,10 @@ static int submodule_repo_create(
* <repo-dir>/modules/<name>/ with a gitlink in the * <repo-dir>/modules/<name>/ with a gitlink in the
* sub-repo workdir directory to that repository. * sub-repo workdir directory to that repository.
*/ */
error = git_buf_join3( error = git_repository_item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES);
&repodir, '/', git_repository_path(parent_repo), "modules", path); if (error < 0)
goto cleanup;
error = git_buf_joinpath(&repodir, repodir.ptr, path);
if (error < 0) if (error < 0)
goto cleanup; goto cleanup;
......
...@@ -375,7 +375,8 @@ static int local_push( ...@@ -375,7 +375,8 @@ static int local_push(
goto on_error; 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; goto on_error;
error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs); error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs);
......
/*
* 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;
}
/*
* 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) ...@@ -613,9 +613,11 @@ void test_iterator_workdir__filesystem2(void)
"heads/ident", "heads/ident",
"heads/long-file-name", "heads/long-file-name",
"heads/master", "heads/master",
"heads/merge-conflict",
"heads/packed-test", "heads/packed-test",
"heads/subtrees", "heads/subtrees",
"heads/test", "heads/test",
"heads/testrepo-worktree",
"tags/e90810b", "tags/e90810b",
"tags/foo/bar", "tags/foo/bar",
"tags/foo/foo/bar", "tags/foo/foo/bar",
...@@ -628,7 +630,7 @@ void test_iterator_workdir__filesystem2(void) ...@@ -628,7 +630,7 @@ void test_iterator_workdir__filesystem2(void)
cl_git_pass(git_iterator_for_filesystem( cl_git_pass(git_iterator_for_filesystem(
&i, "testrepo/.git/refs", NULL)); &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); git_iterator_free(i);
} }
......
...@@ -36,7 +36,7 @@ void test_refs_list__all(void) ...@@ -36,7 +36,7 @@ void test_refs_list__all(void)
/* We have exactly 12 refs in total if we include the packed ones: /* 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 * there is a reference that exists both in the packfile and as
* loose, but we only list it once */ * 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); git_strarray_free(&ref_list);
} }
...@@ -51,7 +51,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten ...@@ -51,7 +51,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten
"144344043ba4d4a405da03de3844aa829ae8be0e\n"); "144344043ba4d4a405da03de3844aa829ae8be0e\n");
cl_git_pass(git_reference_list(&ref_list, g_repo)); 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); git_strarray_free(&ref_list);
} }
gitdir: ../submodules/testrepo/.git/worktrees/submodules-worktree-child
[submodule "testrepo"]
path = testrepo
url = /Users/rb/src/libgit2/tests/resources/testrepo.git
gitdir: ../submodules/.git/worktrees/submodules-worktree-parent
0000000000000000000000000000000000000000 97896810b3210244a62a82458b8e0819ecfc6850 Patrick Steinhardt <ps@pks.im> 1447084240 +0100 branch: Created from HEAD
0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Patrick Steinhardt <ps@pks.im> 1447084252 +0100 branch: Created from HEAD
gitdir: ../testrepo/.git/worktrees/testrepo-worktree
new.txt
\ No newline at end of file
0000000000000000000000000000000000000000 099fabac3a9ea935598528c27f866e34089c2eff Patrick Steinhardt <ps@pks.im> 1442484463 +0200 branch: Created from HEAD
a38d028f71eaa590febb7d716b1ca32350cf70da
099fabac3a9ea935598528c27f866e34089c2eff 099fabac3a9ea935598528c27f866e34089c2eff Patrick Steinhardt <ps@pks.im> 1442484463 +0200 checkout: moving from 099fabac3a9ea935598528c27f866e34089c2eff to testrepo-worktree
...@@ -177,7 +177,7 @@ void test_revwalk_basic__glob_heads_with_invalid(void) ...@@ -177,7 +177,7 @@ void test_revwalk_basic__glob_heads_with_invalid(void)
/* walking */; /* walking */;
/* git log --branches --oneline | wc -l => 16 */ /* 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) void test_revwalk_basic__push_head(void)
......
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
#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);
}
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);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment