worktree: add "lock" command

Helped-by: Eric Sunshine <sunshine@sunshineco.com> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Jun 13, 2016 at 19:18 UTC 58142c09a4fe825912e5a2ebfa1ba5f7f6d8beb5
4 files changed +110 -7
Documentation/git-worktree.txt
+20 -6
@@ -11,6 +11,7 @@ SYNOPSIS
11 [verse]
12 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>]
13 'git worktree list' [--porcelain]
14 +'git worktree lock' [--reason <string>] <worktree>
15 'git worktree prune' [-n] [-v] [--expire <expire>]
16
17 DESCRIPTION
@@ -38,9 +39,8 @@ section "DETAILS" for more information.
39
40 If a linked working tree is stored on a portable device or network share
41 which is not always mounted, you can prevent its administrative files from
41 -being pruned by creating a file named 'locked' alongside the other
42 -administrative files, optionally containing a plain text reason that
43 -pruning should be suppressed. See section "DETAILS" for more information.
42 +being pruned by issuing the `git worktree lock` command, optionally
43 +specifying `--reason` to explain why the working tree is locked.
44
45 COMMANDS
46 --------
@@ -61,6 +61,14 @@ each of the linked worktrees. The output details include if the worktree is
61 bare, the revision currently checked out, and the branch currently checked out
62 (or 'detached HEAD' if none).
63
64 +lock::
65 +
66 +If a working tree is on a portable device or network share which
67 +is not always mounted, lock it to prevent its administrative
68 +files from being pruned automatically. This also prevents it from
69 +being moved or deleted. Optionally, specify a reason for the lock
70 +with `--reason`.
71 +
72 prune::
73
74 Prune working tree information in $GIT_DIR/worktrees.
@@ -110,6 +118,13 @@ OPTIONS
118 --expire <time>::
119 With `prune`, only expire unused working trees older than <time>.
120
121 +--reason <string>::
122 + With `lock`, an explanation why the working tree is locked.
123 +
124 +<worktree>::
125 + Working trees can be identified by path, either relative or
126 + absolute.
127 +
128 DETAILS
129 -------
130 Each linked working tree has a private sub-directory in the repository's
@@ -150,7 +165,8 @@ instead.
165
166 To prevent a $GIT_DIR/worktrees entry from being pruned (which
167 can be useful in some situations, such as when the
153 -entry's working tree is stored on a portable device), add a file named
168 +entry's working tree is stored on a portable device), use the
169 +`git worktree lock` command, which adds a file named
170 'locked' to the entry's directory. The file contains the reason in
171 plain text. For example, if a linked working tree's `.git` file points
172 to `/path/main/.git/worktrees/test-next` then a file named
@@ -226,8 +242,6 @@ performed manually, such as:
242 - `remove` to remove a linked working tree and its administrative files (and
243 warn if the working tree is dirty)
244 - `mv` to move or rename a working tree and update its administrative files
229 -- `lock` to prevent automatic pruning of administrative files (for instance,
230 - for a working tree on a portable device)
245
246 GIT
247 ---
builtin/worktree.c
+38
@@ -14,6 +14,7 @@
14 static const char * const worktree_usage[] = {
15 N_("git worktree add [<options>] <path> [<branch>]"),
16 N_("git worktree list [<options>]"),
17 + N_("git worktree lock [<options>] <path>"),
18 N_("git worktree prune [<options>]"),
19 NULL
20 };
@@ -459,6 +460,41 @@ static int list(int ac, const char **av, const char *prefix)
460 return 0;
461 }
462
463 +static int lock_worktree(int ac, const char **av, const char *prefix)
464 +{
465 + const char *reason = "", *old_reason;
466 + struct option options[] = {
467 + OPT_STRING(0, "reason", &reason, N_("string"),
468 + N_("reason for locking")),
469 + OPT_END()
470 + };
471 + struct worktree **worktrees, *wt;
472 +
473 + ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
474 + if (ac != 1)
475 + usage_with_options(worktree_usage, options);
476 +
477 + worktrees = get_worktrees();
478 + wt = find_worktree(worktrees, prefix, av[0]);
479 + if (!wt)
480 + die(_("'%s' is not a working tree"), av[0]);
481 + if (is_main_worktree(wt))
482 + die(_("The main working tree cannot be locked or unlocked"));
483 +
484 + old_reason = is_worktree_locked(wt);
485 + if (old_reason) {
486 + if (*old_reason)
487 + die(_("'%s' is already locked, reason: %s"),
488 + av[0], old_reason);
489 + die(_("'%s' is already locked"), av[0]);
490 + }
491 +
492 + write_file(git_common_path("worktrees/%s/locked", wt->id),
493 + "%s", reason);
494 + free_worktrees(worktrees);
495 + return 0;
496 +}
497 +
498 int cmd_worktree(int ac, const char **av, const char *prefix)
499 {
500 struct option options[] = {
@@ -475,5 +511,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
511 return prune(ac - 1, av + 1, prefix);
512 if (!strcmp(av[1], "list"))
513 return list(ac - 1, av + 1, prefix);
514 + if (!strcmp(av[1], "lock"))
515 + return lock_worktree(ac - 1, av + 1, prefix);
516 usage_with_options(worktree_usage, options);
517 }
contrib/completion/git-completion.bash
+4 -1
@@ -2597,7 +2597,7 @@ _git_whatchanged ()
2597
2598 _git_worktree ()
2599 {
2600 - local subcommands="add list prune"
2600 + local subcommands="add list lock prune"
2601 local subcommand="$(__git_find_on_cmdline "$subcommands")"
2602 if [ -z "$subcommand" ]; then
2603 __gitcomp "$subcommands"
@@ -2609,6 +2609,9 @@ _git_worktree ()
2609 list,--*)
2610 __gitcomp "--porcelain"
2611 ;;
2612 + lock,--*)
2613 + __gitcomp "--reason"
2614 + ;;
2615 prune,--*)
2616 __gitcomp "--dry-run --expire --verbose"
2617 ;;
t/t2028-worktree-move.sh new
+48
@@ -0,0 +1,48 @@
1 +#!/bin/sh
2 +
3 +test_description='test git worktree move, remove, lock and unlock'
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_success 'setup' '
8 + test_commit init &&
9 + git worktree add source &&
10 + git worktree list --porcelain | grep "^worktree" >actual &&
11 + cat <<-EOF >expected &&
12 + worktree $(pwd)
13 + worktree $(pwd)/source
14 + EOF
15 + test_cmp expected actual
16 +'
17 +
18 +test_expect_success 'lock main worktree' '
19 + test_must_fail git worktree lock .
20 +'
21 +
22 +test_expect_success 'lock linked worktree' '
23 + git worktree lock --reason hahaha source &&
24 + echo hahaha >expected &&
25 + test_cmp expected .git/worktrees/source/locked
26 +'
27 +
28 +test_expect_success 'lock linked worktree from another worktree' '
29 + rm .git/worktrees/source/locked &&
30 + git worktree add elsewhere &&
31 + git -C elsewhere worktree lock --reason hahaha ../source &&
32 + echo hahaha >expected &&
33 + test_cmp expected .git/worktrees/source/locked
34 +'
35 +
36 +test_expect_success 'lock worktree twice' '
37 + test_must_fail git worktree lock source &&
38 + echo hahaha >expected &&
39 + test_cmp expected .git/worktrees/source/locked
40 +'
41 +
42 +test_expect_success 'lock worktree twice (from the locked worktree)' '
43 + test_must_fail git -C source worktree lock . &&
44 + echo hahaha >expected &&
45 + test_cmp expected .git/worktrees/source/locked
46 +'
47 +
48 +test_done