Including functions to get the list of all worktrees, and to get a specific worktree (primary or linked). Signed-off-by: Michael Rappazzo <rappazzo@xxxxxxxxx> --- Makefile | 1 + worktree.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ worktree.h | 48 +++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 worktree.c create mode 100644 worktree.h diff --git a/Makefile b/Makefile index e326fa0..0131fed 100644 --- a/Makefile +++ b/Makefile @@ -807,6 +807,7 @@ LIB_OBJS += version.o LIB_OBJS += versioncmp.o LIB_OBJS += walker.o LIB_OBJS += wildmatch.o +LIB_OBJS += worktree.o LIB_OBJS += wrapper.o LIB_OBJS += write_or_die.o LIB_OBJS += ws.o diff --git a/worktree.c b/worktree.c new file mode 100644 index 0000000..33d2e57 --- /dev/null +++ b/worktree.c @@ -0,0 +1,157 @@ +#include "worktree.h" +#include "cache.h" +#include "git-compat-util.h" +#include "refs.h" +#include "strbuf.h" + +void worktree_release(struct worktree *worktree) +{ + if (worktree) { + free(worktree->path); + free(worktree->git_dir); + if (!worktree->is_detached) { + /* could be headless */ + free(worktree->head_ref); + } + free(worktree); + } +} + +void worktree_list_release(struct worktree_list *worktree_list) +{ + while (worktree_list) { + struct worktree_list *next = worktree_list->next; + worktree_release(worktree_list->worktree); + free (worktree_list); + worktree_list = next; + } +} + +/* + * read 'path_to_ref' into 'ref'. Also set is_detached to 1 if the ref is detatched + * + * return 1 if the ref is not a proper ref, 0 otherwise (success) + */ +int _parse_ref(char *path_to_ref, struct strbuf *ref, int *is_detached) +{ + if (!strbuf_readlink(ref, path_to_ref, 0)) { + if (!starts_with(ref->buf, "refs/") || check_refname_format(ref->buf, 0)) { + /* invalid ref - something is awry with this repo */ + return 1; + } + } else if (strbuf_read_file(ref, path_to_ref, 0) >= 0) { + if (starts_with(ref->buf, "ref:")) { + strbuf_remove(ref, 0, strlen("ref:")); + strbuf_trim(ref); + } else if (is_detached) { + *is_detached = 1; + } + } + return 0; +} + +struct worktree *get_worktree(const char *id) +{ + struct worktree *worktree = NULL; + struct strbuf path = STRBUF_INIT; + struct strbuf worktree_path = STRBUF_INIT; + struct strbuf git_dir = STRBUF_INIT; + struct strbuf head_ref = STRBUF_INIT; + int is_bare = 0; + int is_detached = 0; + + if (id) { + strbuf_addf(&git_dir, "%s/worktrees/%s", absolute_path(get_git_common_dir()), id); + strbuf_addf(&path, "%s/gitdir", git_dir.buf); + if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0) { + /* invalid git_dir file */ + goto done; + } + strbuf_rtrim(&worktree_path); + if (!strbuf_strip_suffix(&worktree_path, "/.git")) { + strbuf_reset(&worktree_path); + strbuf_addstr(&worktree_path, absolute_path(".")); + strbuf_strip_suffix(&worktree_path, "/."); + } + + strbuf_reset(&path); + strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id); + } else { + strbuf_addf(&git_dir, "%s", absolute_path(get_git_common_dir())); + strbuf_addf(&worktree_path, "%s", absolute_path(get_git_common_dir())); + is_bare = !strbuf_strip_suffix(&worktree_path, "/.git"); + if (is_bare) + strbuf_strip_suffix(&worktree_path, "/."); + + strbuf_addf(&path, "%s/HEAD", get_git_common_dir()); + } + + /* + * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside $GIT_DIR so for linked worktrees, + * `resolve_ref_unsafe()` won't work (it uses git_path). Parse the ref ourselves. + */ + if (_parse_ref(path.buf, &head_ref, &is_detached)) + goto done; + + worktree = malloc(sizeof(struct worktree)); + worktree->path = strbuf_detach(&worktree_path, NULL); + worktree->git_dir = strbuf_detach(&git_dir, NULL); + worktree->is_bare = is_bare; + worktree->head_ref = NULL; + worktree->is_detached = is_detached; + if (strlen(head_ref.buf) > 0) { + if (!is_detached) { + resolve_ref_unsafe(head_ref.buf, 0, worktree->head_sha1, NULL); + worktree->head_ref = strbuf_detach(&head_ref, NULL); + } else { + get_sha1_hex(head_ref.buf, worktree->head_sha1); + } + } +done: + strbuf_release(&path); + strbuf_release(&git_dir); + strbuf_release(&head_ref); + strbuf_release(&worktree_path); + return worktree; +} + +struct worktree_list *get_worktree_list() +{ + struct worktree_list *list = NULL; + struct worktree_list *current_entry = NULL; + struct worktree *current_worktree = NULL; + struct strbuf path = STRBUF_INIT; + DIR *dir; + struct dirent *d; + + current_worktree = get_worktree(NULL); + if (current_worktree) { + list = malloc(sizeof(struct worktree_list)); + list->worktree = current_worktree; + list->next = NULL; + current_entry = list; + } + strbuf_addf(&path, "%s/worktrees", get_git_common_dir()); + dir = opendir(path.buf); + strbuf_release(&path); + if (dir) { + while ((d = readdir(dir)) != NULL) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + + current_worktree = get_worktree(d->d_name); + if (current_worktree) { + current_entry->next = malloc(sizeof(struct worktree_list)); + current_entry = current_entry->next; + current_entry->worktree = current_worktree; + current_entry->next = NULL; + } + } + closedir(dir); + } + +done: + + return list; +} + diff --git a/worktree.h b/worktree.h new file mode 100644 index 0000000..2bc0ab8 --- /dev/null +++ b/worktree.h @@ -0,0 +1,48 @@ +#ifndef WORKTREE_H +#define WORKTREE_H + +struct worktree { + char *path; + char *git_dir; + char *head_ref; + unsigned char head_sha1[20]; + int is_detached; + int is_bare; +}; + +struct worktree_list { + struct worktree *worktree; + struct worktree_list *next; +}; + +/* Functions for acting on the information about worktrees. */ + +/* + * Get the list of all worktrees. The primary worktree will always be + * the first in the list followed by any other (linked) worktrees created + * by `git worktree add`. No specific ordering is done on the linked + * worktrees. + * + * The caller is responsible for freeing the memory from the returned list. + * (See worktree_list_release for this purpose). + */ +extern struct worktree_list *get_worktree_list(); + +/* + * generic method to get a worktree + * - if 'id' is NULL, get the from $GIT_COMMON_DIR + * - if 'id' is not NULL, get the worktree found in $GIT_COMMON_DIR/worktrees/id if + * such a worktree exists + * + * The caller is responsible for freeing the memory from the returned + * worktree. (See worktree_release for this purpose) + */ +struct worktree *get_worktree(const char *id); + +/* + * Free up the memory for a worktree_list/worktree + */ +extern void worktree_list_release(struct worktree_list *); +extern void worktree_release(struct worktree *); + +#endif -- 2.5.0 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html