From mboxrd@z Thu Jan 1 00:00:00 1970 From: Michael Rappazzo Subject: [PATCH v2] worktree: list operation Date: Sat, 8 Aug 2015 20:19:59 -0400 Message-ID: <1439079599-87904-2-git-send-email-rappazzo@gmail.com> References: <1439079599-87904-1-git-send-email-rappazzo@gmail.com> Cc: git@vger.kernel.org, Michael Rappazzo To: gitster@pobox.com, sunshine@sunshineco.com X-From: git-owner@vger.kernel.org Sun Aug 09 02:20:39 2015 Return-path: Envelope-to: gcvg-git-2@plane.gmane.org Received: from vger.kernel.org ([209.132.180.67]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1ZOELt-0004Ge-M9 for gcvg-git-2@plane.gmane.org; Sun, 09 Aug 2015 02:20:38 +0200 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2992804AbbHIAUe (ORCPT ); Sat, 8 Aug 2015 20:20:34 -0400 Received: from mail-qk0-f174.google.com ([209.85.220.174]:33336 "EHLO mail-qk0-f174.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2992732AbbHIAUI (ORCPT ); Sat, 8 Aug 2015 20:20:08 -0400 Received: by qkdg63 with SMTP id g63so48317176qkd.0 for ; Sat, 08 Aug 2015 17:20:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=ze+M2YLjR9LL9UhcZsKSsO41M/IrdBe0DASS6ywq1RI=; b=YrseSeyK2b+Ih/o7WQqkviydk7O2nv0UsdNuhyP8R1f/AN8wVV7u1BwNNZCiLbf8g8 MQZcqxq82AbTRpPMJyRJFMCsDy3XnfhZScA5DptyIkFEehw6zRFqvGMHXu/NMjj8/Fsy J2XIbR44dAiBrOVsa7au6plhfX1qyHiMyjJenzHdRn7oDd9xSWjQIi+lZonuHsTb7nsi /A/rO9fr2wxbnAoUuwP9hdwSYqJ1p/jvt0gC2pmo4+q0Gj42qWyPjbWLGLDcE78Q8lYY m99hUGkIhQ1gimXfkfmi1V/KOrNj9FyQqLCu5qtDiKlcLp9DHl/VBnhl3SCHT4sMzVtW wDQw== X-Received: by 10.55.16.147 with SMTP id 19mr26253647qkq.93.1439079607648; Sat, 08 Aug 2015 17:20:07 -0700 (PDT) Received: from MRappazzo.local.info (ool-18e49664.dyn.optonline.net. [24.228.150.100]) by smtp.gmail.com with ESMTPSA id 36sm7380325qgp.8.2015.08.08.17.20.06 (version=TLSv1 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sat, 08 Aug 2015 17:20:07 -0700 (PDT) X-Mailer: git-send-email 2.5.0 In-Reply-To: <1439079599-87904-1-git-send-email-rappazzo@gmail.com> Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Archived-At: 'git worktree list' will list the main worktree followed by any linked worktrees which were created using 'git worktree add'. The option '--main-only' will restrict the list to only the main worktree. --- Documentation/git-worktree.txt | 9 ++++- builtin/worktree.c | 84 ++++++++++++++++++++++++++++++++++++++---- t/t2027-worktree-list.sh | 68 ++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 9 deletions(-) create mode 100755 t/t2027-worktree-list.sh diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 3387e2f..2b6b543 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git worktree add' [-f] [--detach] [-b ] [] 'git worktree prune' [-n] [-v] [--expire ] +'git worktree list' [--main-only] DESCRIPTION ----------- @@ -59,6 +60,10 @@ prune:: Prune working tree information in $GIT_DIR/worktrees. +list:: + +List the main worktree followed by all of the linked worktrees. + OPTIONS ------- @@ -86,6 +91,9 @@ OPTIONS With `prune`, do not remove anything; just report what it would remove. +--main-only:: + With `list`, only list the main worktree. + -v:: --verbose:: With `prune`, report all removals. @@ -167,7 +175,6 @@ performed manually, such as: - `remove` to remove a linked worktree and its administrative files (and warn if the worktree is dirty) - `mv` to move or rename a worktree and update its administrative files -- `list` to list linked worktrees - `lock` to prevent automatic pruning of administrative files (for instance, for a worktree on a portable device) diff --git a/builtin/worktree.c b/builtin/worktree.c index 6a264ee..8c4a82a 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -10,6 +10,7 @@ static const char * const worktree_usage[] = { N_("git worktree add [] "), N_("git worktree prune []"), + N_("git worktree list []"), NULL }; @@ -36,7 +37,7 @@ static int prune_worktree(const char *id, struct strbuf *reason) fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); if (fd < 0) { strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), - id, strerror(errno)); + id, strerror(errno)); return 1; } len = st.st_size; @@ -59,7 +60,7 @@ static int prune_worktree(const char *id, struct strbuf *reason) * accessed since? */ if (!stat(git_path("worktrees/%s/link", id), &st_link) && - st_link.st_nlink > 1) + st_link.st_nlink > 1) return 0; if (st.st_mtime <= expire) { strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id); @@ -187,11 +188,11 @@ static int add_worktree(const char *path, const char **child_argv) name = worktree_basename(path, &len); strbuf_addstr(&sb_repo, - git_path("worktrees/%.*s", (int)(path + len - name), name)); + git_path("worktrees/%.*s", (int)(path + len - name), name)); len = sb_repo.len; if (safe_create_leading_directories_const(sb_repo.buf)) die_errno(_("could not create leading directories of '%s'"), - sb_repo.buf); + sb_repo.buf); while (!stat(sb_repo.buf, &st)) { counter++; strbuf_setlen(&sb_repo, len); @@ -218,14 +219,14 @@ static int add_worktree(const char *path, const char **child_argv) strbuf_addf(&sb_git, "%s/.git", path); if (safe_create_leading_directories_const(sb_git.buf)) die_errno(_("could not create leading directories of '%s'"), - sb_git.buf); + sb_git.buf); junk_work_tree = xstrdup(path); strbuf_reset(&sb); strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf)); write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n", - real_path(get_git_common_dir()), name); + real_path(get_git_common_dir()), name); /* * This is to keep resolve_ref() happy. We need a valid HEAD * or is_git_directory() will reject the directory. Moreover, HEAD @@ -280,9 +281,9 @@ static int add(int ac, const char **av, const char *prefix) struct option options[] = { OPT__FORCE(&force, N_("checkout even if already checked out in other worktree")), OPT_STRING('b', NULL, &new_branch, N_("branch"), - N_("create a new branch")), + N_("create a new branch")), OPT_STRING('B', NULL, &new_branch_force, N_("branch"), - N_("create or reset a branch")), + N_("create or reset a branch")), OPT_BOOL(0, "detach", &detach, N_("detach HEAD at named commit")), OPT_END() }; @@ -316,6 +317,71 @@ static int add(int ac, const char **av, const char *prefix) return add_worktree(path, cmd.argv); } +static int list(int ac, const char **av, const char *prefix) +{ + int main_only = 0; + struct option options[] = { + OPT_BOOL(0, "main-only", &main_only, N_("only list the main worktree")), + OPT_END() + }; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac) + usage_with_options(worktree_usage, options); + + const char *work_tree; + work_tree = get_git_work_tree(); + if (!work_tree) + die("This operation must be run in a work tree"); + + struct strbuf worktree_git_path = STRBUF_INIT; + strbuf_addf(&worktree_git_path, _("%s/.git"), work_tree); + + struct strbuf main_work_tree = STRBUF_INIT; + if (is_directory(worktree_git_path.buf)) { + /* This is the main tree */ + strbuf_addstr(&main_work_tree, work_tree); + } else { + const char *git_dir = get_git_dir(); + strbuf_addf(&main_work_tree, "%.*s", (int)(strstr(git_dir, "/.git/") - git_dir), git_dir); + } + printf("%s\n", main_work_tree.buf); + + if (!main_only) { + chdir( main_work_tree.buf ); + if ( is_directory(git_path("worktrees")) ) { + DIR *dir = opendir( git_path("worktrees") ); + if (dir != NULL) { + struct dirent *d; + struct stat st; + char *path; + int fd, len; + while ((d = readdir(dir)) != NULL) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + if (stat(git_path("worktrees/%s/gitdir", d->d_name), &st)) + continue; + fd = open(git_path("worktrees/%s/gitdir", d->d_name), O_RDONLY); + if (fd < 0) + continue; + + len = st.st_size; + path = xmalloc(len + 1); + read_in_full(fd, path, len); + close(fd); + + printf("%.*s\n", (int)(strstr(path, "/.git") - path), path); + free(path); + } + } + closedir(dir); + } + } + strbuf_release(&main_work_tree); + strbuf_release(&worktree_git_path); + return 0; +} + int cmd_worktree(int ac, const char **av, const char *prefix) { struct option options[] = { @@ -328,5 +394,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix) return add(ac - 1, av + 1, prefix); if (!strcmp(av[1], "prune")) return prune(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "list")) + return list(ac - 1, av + 1, prefix); usage_with_options(worktree_usage, options); } diff --git a/t/t2027-worktree-list.sh b/t/t2027-worktree-list.sh new file mode 100755 index 0000000..998b34f --- /dev/null +++ b/t/t2027-worktree-list.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +test_description='test git worktree list' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init +' + + +test_expect_success '"list" all worktrees from main' ' + orig_path=$PWD && + git rev-parse --show-toplevel >expect && + git worktree add --detach here master && + ( + cd here && + git rev-parse --show-toplevel >>"$orig_path/expect" && + cd "$orig_path" && + git worktree list >actual && + test_cmp expect actual && + rm -rf here && + git worktree prune + ) +' +test_expect_success '"list" all worktrees from linked' ' + orig_path=$PWD && + git rev-parse --show-toplevel >expect && + git worktree add --detach here master && + ( + cd here && + git rev-parse --show-toplevel >>"$orig_path/expect" && + git worktree list >actual && + test_cmp "$orig_path/expect" actual && + cd "$orig_path" && + rm -rf here && + git worktree prune + ) +' + +test_expect_success '"list" main worktree from main' ' + orig_path=$PWD && + git rev-parse --show-toplevel >expect && + git worktree add --detach here master && + ( + cd here && + cd "$orig_path" && + git worktree list --main-only >actual && + test_cmp expect actual && + rm -rf here && + git worktree prune + ) +' +test_expect_success '"list" main worktree from linked' ' + orig_path=$PWD && + git rev-parse --show-toplevel >expect && + git worktree add --detach here master && + ( + cd here && + git worktree list --main-only >actual && + test_cmp "$orig_path/expect" actual && + cd "$orig_path" && + rm -rf here && + git worktree prune + ) +' + +test_done -- 2.5.0