summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@github.com>2017-02-13 11:10:49 +0000
committerGitHub <noreply@github.com>2017-02-13 11:10:49 +0000
commit4f9f8e0dc9ca8a912fe1b32aeca545d25790d3fe (patch)
tree404ecc3bc1014661b72c99076e72f3da54aca0a5 /src
parent43275f512ef888a14c43bde8007251ce595be32a (diff)
parent1ba242c9ab0eb323abed1b3bbc770aeb3367d855 (diff)
downloadlibgit2-4f9f8e0dc9ca8a912fe1b32aeca545d25790d3fe.tar.gz
Merge pull request #3436 from pks-t/libgit2-worktree
Worktree implementation
Diffstat (limited to 'src')
-rw-r--r--src/attr.c36
-rw-r--r--src/attr_file.h2
-rw-r--r--src/blob.c4
-rw-r--r--src/branch.c63
-rw-r--r--src/cherrypick.c4
-rw-r--r--src/clone.c5
-rw-r--r--src/fetchhead.c4
-rw-r--r--src/ignore.c8
-rw-r--r--src/ignore.h2
-rw-r--r--src/merge.c10
-rw-r--r--src/rebase.c6
-rw-r--r--src/refdb_fs.c87
-rw-r--r--src/repository.c363
-rw-r--r--src/repository.h7
-rw-r--r--src/revert.c4
-rw-r--r--src/submodule.c12
-rw-r--r--src/transports/local.c3
-rw-r--r--src/worktree.c432
-rw-r--r--src/worktree.h35
19 files changed, 968 insertions, 119 deletions
diff --git a/src/attr.c b/src/attr.c
index d43a15f50..93dea123f 100644
--- a/src/attr.c
+++ b/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;
diff --git a/src/attr_file.h b/src/attr_file.h
index 388ecf4c0..a9af2403a 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -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"
diff --git a/src/blob.c b/src/blob.c
index cd5df3537..19d3039fb 100644
--- a/src/blob.c
+++ b/src/blob.c
@@ -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,
diff --git a/src/branch.c b/src/branch.c
index 7ddcb3da7..7d5e9cb7f 100644
--- a/src/branch.c
+++ b/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;
diff --git a/src/cherrypick.c b/src/cherrypick.c
index ab067339e..d8b6858ae 100644
--- a/src/cherrypick.c
+++ b/src/cherrypick.c
@@ -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;
diff --git a/src/clone.c b/src/clone.c
index 0d4756e28..16ddface2 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -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;
}
diff --git a/src/fetchhead.c b/src/fetchhead.c
index 0d9ab2c25..6e6f3eb5e 100644
--- a/src/fetchhead.c
+++ b/src/fetchhead.c
@@ -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)
diff --git a/src/ignore.c b/src/ignore.c
index cc9e08e35..c324d4dd4 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -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);
diff --git a/src/ignore.h b/src/ignore.h
index d40bd60f9..876c8e0ea 100644
--- a/src/ignore.h
+++ b/src/ignore.h
@@ -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:
diff --git a/src/merge.c b/src/merge.c
index eceadf08a..857d51311 100644
--- a/src/merge.c
+++ b/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;
diff --git a/src/rebase.c b/src/rebase.c
index b2024a439..09941a2a2 100644
--- a/src/rebase.c
+++ b/src/rebase.c
@@ -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);
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
index e40f48bd5..3d690630e 100644
--- a/src/refdb_fs.c
+++ b/src/refdb_fs.c
@@ -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;
}
diff --git a/src/repository.c b/src/repository.c
index 2185632bf..4b937be20 100644
--- a/src/repository.c
+++ b/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;
}
@@ -225,9 +276,10 @@ static int load_config_data(git_repository *repo, const git_config *config)
static int load_workdir(git_repository *repo, git_config *config, git_buf *parent_path)
{
- int error;
+ int error;
git_config_entry *ce;
- git_buf worktree = GIT_BUF_INIT;
+ 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);
diff --git a/src/repository.h b/src/repository.h
index 9d276f376..c328ecd21 100644
--- a/src/repository.h
+++ b/src/repository.h
@@ -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.
diff --git a/src/revert.c b/src/revert.c
index b255245bf..747938fb3 100644
--- a/src/revert.c
+++ b/src/revert.c
@@ -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)
diff --git a/src/submodule.c b/src/submodule.c
index 1c17075bf..3007d25df 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -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;
diff --git a/src/transports/local.c b/src/transports/local.c
index 87745add5..e24e99860 100644
--- a/src/transports/local.c
+++ b/src/transports/local.c
@@ -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);
diff --git a/src/worktree.c b/src/worktree.c
new file mode 100644
index 000000000..5abc98945
--- /dev/null
+++ b/src/worktree.c
@@ -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;
+}
diff --git a/src/worktree.h b/src/worktree.h
new file mode 100644
index 000000000..b8e527968
--- /dev/null
+++ b/src/worktree.h
@@ -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