diff options
Diffstat (limited to 'src/repository.c')
-rw-r--r-- | src/repository.c | 620 |
1 files changed, 484 insertions, 136 deletions
diff --git a/src/repository.c b/src/repository.c index cf3d18a2d..fe549e6e8 100644 --- a/src/repository.c +++ b/src/repository.c @@ -4,6 +4,9 @@ * 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 "repository.h" + #include <ctype.h> #include "git2/object.h" @@ -11,7 +14,6 @@ #include "git2/sys/repository.h" #include "common.h" -#include "repository.h" #include "commit.h" #include "tag.h" #include "blob.h" @@ -27,13 +29,43 @@ #include "merge.h" #include "diff_driver.h" #include "annotated_commit.h" +#include "submodule.h" +#include "worktree.h" + +#include "strmap.h" #ifdef GIT_WIN32 # include "win32/w32_util.h" #endif +bool git_repository__fsync_gitdir = false; + +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" @@ -109,6 +141,7 @@ void git_repository__cleanup(git_repository *repo) { assert(repo); + git_repository_submodule_cache_clear(repo); git_cache_clear(&repo->objects); git_attr_cache_flush(repo); @@ -136,8 +169,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); @@ -152,17 +186,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; @@ -201,6 +259,7 @@ int git_repository_new(git_repository **out) GITERR_CHECK_ALLOC(repo); repo->is_bare = 1; + repo->is_worktree = 0; return 0; } @@ -220,9 +279,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; @@ -231,9 +291,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); @@ -241,7 +318,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; @@ -252,6 +329,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; } @@ -332,7 +410,7 @@ static int read_gitfile(git_buf *path_out, const char *file_path) memcmp(git_buf_cstr(&file), GIT_FILE_CONTENT_PREFIX, prefix_len) != 0) { giterr_set(GITERR_REPOSITORY, - "The `.git` file at '%s' is malformed", file_path); + "the `.git` file at '%s' is malformed", file_path); error = -1; } else if ((error = git_path_dirname_r(path_out, file_path)) >= 0) { @@ -348,9 +426,10 @@ static int read_gitfile(git_buf *path_out, const char *file_path) } static int find_repo( - git_buf *repo_path, - git_buf *parent_path, - git_buf *link_path, + git_buf *gitdir_path, + git_buf *workdir_path, + git_buf *gitlink_path, + git_buf *commondir_path, const char *start_path, uint32_t flags, const char *ceiling_dirs) @@ -358,13 +437,14 @@ 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; bool in_dot_git; size_t ceiling_offset = 0; - git_buf_free(repo_path); + git_buf_clear(gitdir_path); error = git_path_prettify(&path, start_path, NULL); if (error < 0) @@ -404,21 +484,30 @@ 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); + git_buf_set(gitdir_path, path.ptr, path.size); + + if (gitlink_path) + git_buf_attach(gitlink_path, + git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0); + if (commondir_path) + git_buf_swap(&common_link, commondir_path); + break; } } - else if (S_ISREG(st.st_mode)) { + else if (S_ISREG(st.st_mode) && git__suffixcmp(path.ptr, "/" DOT_GIT) == 0) { error = read_gitfile(&repo_link, path.ptr); if (error < 0) break; - if (valid_repository_path(&repo_link)) { - git_buf_swap(repo_path, &repo_link); + if (valid_repository_path(&repo_link, &common_link)) { + git_buf_swap(gitdir_path, &repo_link); - if (link_path) - error = git_buf_put(link_path, path.ptr, path.size); + if (gitlink_path) + error = git_buf_put(gitlink_path, path.ptr, path.size); + if (commondir_path) + git_buf_swap(&common_link, commondir_path); } break; } @@ -444,27 +533,28 @@ static int find_repo( break; } - if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { - if (!git_buf_len(repo_path)) - git_buf_clear(parent_path); + if (!error && workdir_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { + if (!git_buf_len(gitdir_path)) + git_buf_clear(workdir_path); else { - git_path_dirname_r(parent_path, path.ptr); - git_path_to_dir(parent_path); + git_path_dirname_r(workdir_path, path.ptr); + git_path_to_dir(workdir_path); } - if (git_buf_oom(parent_path)) + if (git_buf_oom(workdir_path)) return -1; } /* If we didn't find the repository, and we don't have any other error * to report, report that. */ - if (!git_buf_len(repo_path) && !error) { + if (!git_buf_len(gitdir_path) && !error) { giterr_set(GITERR_REPOSITORY, - "Could not find repository from '%s'", start_path); + "could not find repository from '%s'", start_path); error = GIT_ENOTFOUND; } git_buf_free(&path); git_buf_free(&repo_link); + git_buf_free(&common_link); return error; } @@ -473,26 +563,30 @@ 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); - giterr_set(GITERR_REPOSITORY, "Path is not a repository: %s", bare_path); + git_buf_free(&common_path); + giterr_set(GITERR_REPOSITORY, "path is not a repository: %s", bare_path); return GIT_ENOTFOUND; } 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; @@ -613,9 +707,10 @@ static int _git_repository_open_ext_from_env( git_repository_set_odb(repo, odb); error = git__getenv(&alts_buf, "GIT_ALTERNATE_OBJECT_DIRECTORIES"); - if (error == GIT_ENOTFOUND) + if (error == GIT_ENOTFOUND) { giterr_clear(); - else if (error < 0) + error = 0; + } else if (error < 0) goto error; else { const char *end; @@ -638,9 +733,11 @@ static int _git_repository_open_ext_from_env( } } - error = git_repository_set_namespace(repo, git_buf_cstr(&namespace_buf)); - if (error < 0) - goto error; + if (git_buf_len(&namespace_buf)) { + error = git_repository_set_namespace(repo, git_buf_cstr(&namespace_buf)); + if (error < 0) + goto error; + } git_repository_set_index(repo, index); @@ -665,6 +762,29 @@ success: return error; } +static int repo_is_worktree(unsigned *out, const git_repository *repo) +{ + git_buf gitdir_link = GIT_BUF_INIT; + int error; + + /* Worktrees cannot have the same commondir and gitdir */ + if (repo->commondir && repo->gitdir + && !strcmp(repo->commondir, repo->gitdir)) { + *out = 0; + return 0; + } + + if ((error = git_buf_joinpath(&gitdir_link, repo->gitdir, "gitdir")) < 0) + return -1; + + /* A 'gitdir' file inside a git directory is currently + * only used when the repository is a working tree. */ + *out = !!git_path_exists(gitdir_link.ptr); + + git_buf_free(&gitdir_link); + return error; +} + int git_repository_open_ext( git_repository **repo_ptr, const char *start_path, @@ -672,8 +792,9 @@ int git_repository_open_ext( const char *ceiling_dirs) { int error; - git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT, - link_path = GIT_BUF_INIT; + unsigned is_worktree; + git_buf gitdir = GIT_BUF_INIT, workdir = GIT_BUF_INIT, + gitlink = GIT_BUF_INIT, commondir = GIT_BUF_INIT; git_repository *repo; git_config *config = NULL; @@ -684,7 +805,7 @@ int git_repository_open_ext( *repo_ptr = NULL; error = find_repo( - &path, &parent, &link_path, start_path, flags, ceiling_dirs); + &gitdir, &workdir, &gitlink, &commondir, start_path, flags, ceiling_dirs); if (error < 0 || !repo_ptr) return error; @@ -692,14 +813,22 @@ 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(&gitdir); + GITERR_CHECK_ALLOC(repo->gitdir); - if (link_path.size) { - repo->path_gitlink = git_buf_detach(&link_path); - GITERR_CHECK_ALLOC(repo->path_gitlink); + if (gitlink.size) { + repo->gitlink = git_buf_detach(&gitlink); + GITERR_CHECK_ALLOC(repo->gitlink); + } + if (commondir.size) { + repo->commondir = git_buf_detach(&commondir); + GITERR_CHECK_ALLOC(repo->commondir); } + if ((error = repo_is_worktree(&is_worktree, repo)) < 0) + goto cleanup; + repo->is_worktree = is_worktree; + /* * We'd like to have the config, but git doesn't particularly * care if it's not there, so we need to deal with that. @@ -718,12 +847,13 @@ int git_repository_open_ext( if (config && ((error = load_config_data(repo, config)) < 0 || - (error = load_workdir(repo, config, &parent)) < 0)) + (error = load_workdir(repo, config, &workdir)) < 0)) goto cleanup; } cleanup: - git_buf_free(&parent); + git_buf_free(&gitdir); + git_buf_free(&workdir); git_config_free(config); if (error < 0) @@ -740,6 +870,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; @@ -765,7 +925,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( @@ -785,14 +945,10 @@ 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); - if (error < 0) - goto on_error; + if ((error = git_repository_item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG)) == 0) + error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0); - if ((error = git_config_add_file_ondisk( - cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0 && - error != GIT_ENOTFOUND) + if (error && error != GIT_ENOTFOUND) goto on_error; git_buf_free(&config_path); @@ -920,18 +1076,23 @@ 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 || + (error = git_odb_new(&odb)) < 0) return error; - error = git_odb_open(&odb, odb_path.ptr); - if (!error) { - GIT_REFCOUNT_OWN(odb, repo); + GIT_REFCOUNT_OWN(odb, repo); - odb = git__compare_and_swap(&repo->_odb, NULL, odb); - if (odb != NULL) { - GIT_REFCOUNT_OWN(odb, NULL); - git_odb_free(odb); - } + if ((error = git_odb__set_caps(odb, GIT_ODB_CAP_FROM_OWNER)) < 0 || + (error = git_odb__add_default_backends(odb, odb_path.ptr, 0, 0)) < 0) { + git_odb_free(odb); + return error; + } + + odb = git__compare_and_swap(&repo->_odb, NULL, odb); + if (odb != NULL) { + GIT_REFCOUNT_OWN(odb, NULL); + git_odb_free(odb); } git_buf_free(&odb_path); @@ -1006,7 +1167,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); @@ -1076,7 +1237,7 @@ static int reserved_names_add8dot3(git_repository *repo, const char *path) name_len = strlen(name); - if ((name_len == def_len && memcmp(name, def, def_len) == 0) || + if ((name_len == def_len && memcmp(name, def, def_len) == 0) || (name_len == def_dot_git_len && memcmp(name, def_dot_git, def_dot_git_len) == 0)) { git__free(name); return 0; @@ -1122,13 +1283,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; } } @@ -1177,7 +1338,7 @@ static int check_repositoryformatversion(git_config *config) if (GIT_REPO_VERSION < version) { giterr_set(GITERR_REPOSITORY, - "Unsupported repository version %d. Only versions up to %d are supported.", + "unsupported repository version %d. Only versions up to %d are supported.", version, GIT_REPO_VERSION); return -1; } @@ -1185,7 +1346,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; @@ -1271,12 +1432,12 @@ static int create_empty_file(const char *path, mode_t mode) int fd; if ((fd = p_creat(path, mode)) < 0) { - giterr_set(GITERR_OS, "Error while creating '%s'", path); + giterr_set(GITERR_OS, "error while creating '%s'", path); return -1; } if (p_close(fd) < 0) { - giterr_set(GITERR_OS, "Error while closing '%s'", path); + giterr_set(GITERR_OS, "error while closing '%s'", path); return -1; } @@ -1505,7 +1666,7 @@ static int repo_write_template( if (error) giterr_set(GITERR_OS, - "Failed to initialize repository with template '%s'", file); + "failed to initialize repository with template '%s'", file); return error; } @@ -1536,7 +1697,7 @@ static int repo_write_gitlink( if (!p_stat(buf.ptr, &st) && !S_ISREG(st.st_mode)) { giterr_set(GITERR_REPOSITORY, - "Cannot overwrite gitlink file into path '%s'", in_dir); + "cannot overwrite gitlink file into path '%s'", in_dir); error = GIT_EEXISTS; goto cleanup; } @@ -1590,7 +1751,7 @@ static int repo_init_structure( if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) { if (git_win32__set_hidden(repo_dir, true) < 0) { giterr_set(GITERR_OS, - "Failed to mark Git repository folder as hidden"); + "failed to mark Git repository folder as hidden"); return -1; } } @@ -1625,7 +1786,13 @@ static int repo_init_structure( default_template = true; } - if (tdir) { + /* + * If tdir was the empty string, treat it like tdir was a path to an + * empty directory (so, don't do any copying). This is the behavior + * that git(1) exhibits, although it doesn't seem to be officially + * documented. + */ + if (tdir && git__strcmp(tdir, "") != 0) { uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_SIMPLE_TO_MODE | GIT_CPDIR_COPY_DOTFILES; @@ -1744,7 +1911,7 @@ static int repo_init_directories( if (git_path_dirname_r(wd_path, repo_path->ptr) < 0) return -1; } else { - giterr_set(GITERR_REPOSITORY, "Cannot pick working directory" + giterr_set(GITERR_REPOSITORY, "cannot pick working directory" " for non-bare repository that isn't a '.git' directory"); return -1; } @@ -1848,7 +2015,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); @@ -1860,11 +2028,11 @@ 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, - "Attempt to reinitialize '%s'", given_repo); + "attempt to reinitialize '%s'", given_repo); error = GIT_EEXISTS; goto cleanup; } @@ -1881,7 +2049,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) @@ -1893,6 +2061,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); @@ -1922,6 +2091,29 @@ int git_repository_head_detached(git_repository *repo) return exists; } +static int get_worktree_file_path(git_buf *out, git_repository *repo, const char *worktree, const char *file) +{ + git_buf_clear(out); + return git_buf_printf(out, "%s/worktrees/%s/%s", repo->commondir, worktree, file); +} + +int git_repository_head_detached_for_worktree(git_repository *repo, const char *name) +{ + git_reference *ref = NULL; + int error; + + assert(repo && name); + + if ((error = git_repository_head_for_worktree(&ref, repo, name)) < 0) + goto out; + + error = (git_reference_type(ref) != GIT_REF_SYMBOLIC); +out: + git_reference_free(ref); + + return error; +} + int git_repository_head(git_reference **head_out, git_repository *repo) { git_reference *head; @@ -1941,6 +2133,71 @@ 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 path = GIT_BUF_INIT; + git_reference *head = NULL; + int error; + + assert(out && repo && name); + + *out = NULL; + + if ((error = get_worktree_file_path(&path, repo, name, GIT_HEAD_FILE)) < 0 || + (error = git_reference__read_head(&head, repo, path.ptr)) < 0) + goto out; + + if (git_reference_type(head) != GIT_REF_OID) { + git_reference *resolved; + + error = git_reference_lookup_resolved(&resolved, repo, git_reference_symbolic_target(head), -1); + git_reference_free(head); + head = resolved; + } + + *out = head; + +out: + if (error) + git_reference_free(head); + + git_buf_free(&path); + + return error; +} + +int git_repository_foreach_head(git_repository *repo, git_repository_foreach_head_cb cb, void *payload) +{ + git_strarray worktrees = GIT_VECTOR_INIT; + git_buf path = GIT_BUF_INIT; + int error; + size_t i; + + /* Execute callback for HEAD of commondir */ + if ((error = git_buf_joinpath(&path, repo->commondir, GIT_HEAD_FILE)) < 0 || + (error = cb(repo, path.ptr, payload) != 0)) + goto out; + + if ((error = git_worktree_list(&worktrees, repo)) < 0) { + error = 0; + goto out; + } + + /* Execute callback for all worktree HEADs */ + for (i = 0; i < worktrees.count; i++) { + if (get_worktree_file_path(&path, repo, worktrees.strings[i], GIT_HEAD_FILE) < 0) + continue; + + if ((error = cb(repo, path.ptr, payload)) != 0) + goto out; + } + +out: + git_buf_free(&path); + git_strarray_free(&worktrees); + return error; +} + int git_repository_head_unborn(git_repository *repo) { git_reference *ref = NULL; @@ -1999,10 +2256,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 GIT_ENOTFOUND; + } + + 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) @@ -2015,6 +2312,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) { @@ -2065,6 +2368,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; @@ -2119,7 +2428,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); @@ -2140,13 +2449,13 @@ 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) { if (errno == ENOENT) error = GIT_ENOTFOUND; - giterr_set(GITERR_OS, "Could not access message file"); + giterr_set(GITERR_OS, "could not access message file"); } else { error = git_futils_readbuffer(out, git_buf_cstr(&path)); } @@ -2161,7 +2470,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)); @@ -2224,7 +2533,7 @@ int git_repository_hashfile( } if (!git__is_sizet(len)) { - giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); + giterr_set(GITERR_OS, "file size overflow for 32-bit systems"); error = -1; goto cleanup; } @@ -2251,7 +2560,9 @@ static int checkout_message(git_buf *out, git_reference *old, const char *new) git_buf_puts(out, " to "); - if (git_reference__is_branch(new)) + if (git_reference__is_branch(new) || + git_reference__is_tag(new) || + git_reference__is_remote(new)) git_buf_puts(out, git_reference__shorthand(new)); else git_buf_puts(out, new); @@ -2262,77 +2573,86 @@ static int checkout_message(git_buf *out, git_reference *old, const char *new) return 0; } -int git_repository_set_head( - git_repository* repo, - const char* refname) +static int detach(git_repository *repo, const git_oid *id, const char *new) { - git_reference *ref = NULL, *current = NULL, *new_head = NULL; - git_buf log_message = GIT_BUF_INIT; int error; + git_buf log_message = GIT_BUF_INIT; + git_object *object = NULL, *peeled = NULL; + git_reference *new_head = NULL, *current = NULL; - assert(repo && refname); + assert(repo && id); if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) return error; - if ((error = checkout_message(&log_message, current, refname)) < 0) + if ((error = git_object_lookup(&object, repo, id, GIT_OBJ_ANY)) < 0) goto cleanup; - error = git_reference_lookup(&ref, repo, refname); - if (error < 0 && error != GIT_ENOTFOUND) + if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0) goto cleanup; - if (!error) { - if (git_reference_is_branch(ref)) { - error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, - git_reference_name(ref), true, git_buf_cstr(&log_message)); - } else { - error = git_repository_set_head_detached(repo, git_reference_target(ref)); - } - } else if (git_reference__is_branch(refname)) { - error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, refname, - true, git_buf_cstr(&log_message)); - } + if (new == NULL) + new = git_oid_tostr_s(git_object_id(peeled)); + + if ((error = checkout_message(&log_message, current, new)) < 0) + goto cleanup; + + error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, git_buf_cstr(&log_message)); cleanup: git_buf_free(&log_message); + git_object_free(object); + git_object_free(peeled); git_reference_free(current); - git_reference_free(ref); git_reference_free(new_head); return error; } -static int detach(git_repository *repo, const git_oid *id, const char *from) +int git_repository_set_head( + git_repository* repo, + const char* refname) { - int error; + git_reference *ref = NULL, *current = NULL, *new_head = NULL; git_buf log_message = GIT_BUF_INIT; - git_object *object = NULL, *peeled = NULL; - git_reference *new_head = NULL, *current = NULL; + int error; - assert(repo && id); + assert(repo && refname); if ((error = git_reference_lookup(¤t, repo, GIT_HEAD_FILE)) < 0) return error; - if ((error = git_object_lookup(&object, repo, id, GIT_OBJ_ANY)) < 0) + if ((error = checkout_message(&log_message, current, refname)) < 0) goto cleanup; - if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0) + error = git_reference_lookup(&ref, repo, refname); + if (error < 0 && error != GIT_ENOTFOUND) goto cleanup; - if (from == NULL) - from = git_oid_tostr_s(git_object_id(peeled)); - - if ((error = checkout_message(&log_message, current, from)) < 0) + 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)) { + giterr_set(GITERR_REPOSITORY, "cannot set HEAD to reference '%s' as it is the current HEAD " + "of a linked repository.", git_reference_name(ref)); + error = -1; goto cleanup; + } - error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, git_buf_cstr(&log_message)); + if (!error) { + if (git_reference_is_branch(ref)) { + error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, + git_reference_name(ref), true, git_buf_cstr(&log_message)); + } else { + error = detach(repo, git_reference_target(ref), + git_reference_is_tag(ref) || git_reference_is_remote(ref) ? refname : NULL); + } + } else if (git_reference__is_branch(refname)) { + error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, refname, + true, git_buf_cstr(&log_message)); + } cleanup: git_buf_free(&log_message); - git_object_free(object); - git_object_free(peeled); git_reference_free(current); + git_reference_free(ref); git_reference_free(new_head); return error; } @@ -2397,7 +2717,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)) @@ -2439,7 +2759,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); @@ -2450,7 +2770,7 @@ int git_repository__cleanup_files( error = git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS); } - + git_buf_clear(&buf); } @@ -2483,7 +2803,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); @@ -2538,3 +2858,31 @@ int git_repository_set_ident(git_repository *repo, const char *name, const char return 0; } + +int git_repository_submodule_cache_all(git_repository *repo) +{ + int error; + + assert(repo); + + if ((error = git_strmap_alloc(&repo->submodule_cache))) + return error; + + error = git_submodule__map(repo, repo->submodule_cache); + return error; +} + +int git_repository_submodule_cache_clear(git_repository *repo) +{ + git_submodule *sm; + assert(repo); + if (repo->submodule_cache == NULL) { + return 0; + } + git_strmap_foreach_value(repo->submodule_cache, sm, { + git_submodule_free(sm); + }); + git_strmap_free(repo->submodule_cache); + repo->submodule_cache = 0; + return 0; +} |