| ... | @@ -826,7 +826,7 @@ it.layer(TestLayer)("git integration", (it) => { | ... | @@ -826,7 +826,7 @@ it.layer(TestLayer)("git integration", (it) => { |
| 826 | }), | 826 | }), |
| 827 | ); | 827 | ); |
| 828 | | 828 | |
| 829 | it.effect("shares upstream refreshes across worktrees that use the same git common dir", () => | 829 | it.effect("coalesces upstream refreshes across sibling worktrees on the same remote", () => |
| 830 | Effect.gen(function* () { | 830 | Effect.gen(function* () { |
| 831 | const ok = (stdout = "") => | 831 | const ok = (stdout = "") => |
| 832 | Effect.succeed({ | 832 | Effect.succeed({ |
| ... | @@ -845,7 +845,9 @@ it.layer(TestLayer)("git integration", (it) => { | ... | @@ -845,7 +845,9 @@ it.layer(TestLayer)("git integration", (it) => { |
| 845 | input.args[2] === "--symbolic-full-name" && | 845 | input.args[2] === "--symbolic-full-name" && |
| 846 | input.args[3] === "@{upstream}" | 846 | input.args[3] === "@{upstream}" |
| 847 | ) { | 847 | ) { |
| 848 | return ok("origin/main\n"); | 848 | return ok( |
| | 849 | input.cwd === "/repo/worktrees/pr-123" ? "origin/feature/pr-123\n" : "origin/main\n", |
| | 850 | ); |
| 849 | } | 851 | } |
| 850 | if (input.args[0] === "remote") { | 852 | if (input.args[0] === "remote") { |
| 851 | return ok("origin\n"); | 853 | return ok("origin\n"); |
| ... | @@ -856,10 +858,22 @@ it.layer(TestLayer)("git integration", (it) => { | ... | @@ -856,10 +858,22 @@ it.layer(TestLayer)("git integration", (it) => { |
| 856 | if (input.args[0] === "--git-dir" && input.args[2] === "fetch") { | 858 | if (input.args[0] === "--git-dir" && input.args[2] === "fetch") { |
| 857 | fetchCount += 1; | 859 | fetchCount += 1; |
| 858 | expect(input.cwd).toBe("/repo"); | 860 | expect(input.cwd).toBe("/repo"); |
| | 861 | expect(input.args).toEqual([ |
| | 862 | "--git-dir", |
| | 863 | "/repo/.git", |
| | 864 | "fetch", |
| | 865 | "--quiet", |
| | 866 | "--no-tags", |
| | 867 | "origin", |
| | 868 | ]); |
| 859 | return ok(); | 869 | return ok(); |
| 860 | } | 870 | } |
| 861 | if (input.operation === "GitCore.statusDetails.status") { | 871 | if (input.operation === "GitCore.statusDetails.status") { |
| 862 | return ok("# branch.head main\n# branch.upstream origin/main\n# branch.ab +0 -0\n"); | 872 | return ok( |
| | 873 | input.cwd === "/repo/worktrees/pr-123" |
| | 874 | ? "# branch.head feature/pr-123\n# branch.upstream origin/feature/pr-123\n# branch.ab +0 -0\n" |
| | 875 | : "# branch.head main\n# branch.upstream origin/main\n# branch.ab +0 -0\n", |
| | 876 | ); |
| 863 | } | 877 | } |
| 864 | if ( | 878 | if ( |
| 865 | input.operation === "GitCore.statusDetails.unstagedNumstat" || | 879 | input.operation === "GitCore.statusDetails.unstagedNumstat" || |
| ... | @@ -886,70 +900,80 @@ it.layer(TestLayer)("git integration", (it) => { | ... | @@ -886,70 +900,80 @@ it.layer(TestLayer)("git integration", (it) => { |
| 886 | }), | 900 | }), |
| 887 | ); | 901 | ); |
| 888 | | 902 | |
| 889 | it.effect("briefly backs off failed upstream refreshes across sibling worktrees", () => | 903 | it.effect( |
| 890 | Effect.gen(function* () { | 904 | "briefly backs off failed upstream refreshes across sibling worktrees on one remote", |
| 891 | const ok = (stdout = "") => | 905 | () => |
| 892 | Effect.succeed({ | 906 | Effect.gen(function* () { |
| 893 | code: 0, | 907 | const ok = (stdout = "") => |
| 894 | stdout, | 908 | Effect.succeed({ |
| 895 | stderr: "", | 909 | code: 0, |
| 896 | stdoutTruncated: false, | 910 | stdout, |
| 897 | stderrTruncated: false, | 911 | stderr: "", |
| 898 | }); | 912 | stdoutTruncated: false, |
| | 913 | stderrTruncated: false, |
| | 914 | }); |
| 899 | | 915 | |
| 900 | let fetchCount = 0; | 916 | let fetchCount = 0; |
| 901 | const core = yield* makeIsolatedGitCore((input) => { | 917 | const core = yield* makeIsolatedGitCore((input) => { |
| 902 | if ( | 918 | if ( |
| 903 | input.args[0] === "rev-parse" && | 919 | input.args[0] === "rev-parse" && |
| 904 | input.args[1] === "--abbrev-ref" && | 920 | input.args[1] === "--abbrev-ref" && |
| 905 | input.args[2] === "--symbolic-full-name" && | 921 | input.args[2] === "--symbolic-full-name" && |
| 906 | input.args[3] === "@{upstream}" | 922 | input.args[3] === "@{upstream}" |
| 907 | ) { | 923 | ) { |
| 908 | return ok("origin/main\n"); | 924 | return ok( |
| 909 | } | 925 | input.cwd === "/repo/worktrees/pr-123" |
| 910 | if (input.args[0] === "remote") { | 926 | ? "origin/feature/pr-123\n" |
| 911 | return ok("origin\n"); | 927 | : "origin/main\n", |
| 912 | } | 928 | ); |
| 913 | if (input.args[0] === "rev-parse" && input.args[1] === "--git-common-dir") { | 929 | } |
| 914 | return ok("/repo/.git\n"); | 930 | if (input.args[0] === "remote") { |
| 915 | } | 931 | return ok("origin\n"); |
| 916 | if (input.args[0] === "--git-dir" && input.args[2] === "fetch") { | 932 | } |
| 917 | fetchCount += 1; | 933 | if (input.args[0] === "rev-parse" && input.args[1] === "--git-common-dir") { |
| | 934 | return ok("/repo/.git\n"); |
| | 935 | } |
| | 936 | if (input.args[0] === "--git-dir" && input.args[2] === "fetch") { |
| | 937 | fetchCount += 1; |
| | 938 | return Effect.fail( |
| | 939 | new GitCommandError({ |
| | 940 | operation: input.operation, |
| | 941 | command: `git ${input.args.join(" ")}`, |
| | 942 | cwd: input.cwd, |
| | 943 | detail: "simulated fetch timeout", |
| | 944 | }), |
| | 945 | ); |
| | 946 | } |
| | 947 | if (input.operation === "GitCore.statusDetails.status") { |
| | 948 | return ok( |
| | 949 | input.cwd === "/repo/worktrees/pr-123" |
| | 950 | ? "# branch.head feature/pr-123\n# branch.upstream origin/feature/pr-123\n# branch.ab +0 -0\n" |
| | 951 | : "# branch.head main\n# branch.upstream origin/main\n# branch.ab +0 -0\n", |
| | 952 | ); |
| | 953 | } |
| | 954 | if ( |
| | 955 | input.operation === "GitCore.statusDetails.unstagedNumstat" || |
| | 956 | input.operation === "GitCore.statusDetails.stagedNumstat" |
| | 957 | ) { |
| | 958 | return ok(); |
| | 959 | } |
| | 960 | if (input.operation === "GitCore.statusDetails.defaultRef") { |
| | 961 | return ok("refs/remotes/origin/main\n"); |
| | 962 | } |
| 918 | return Effect.fail( | 963 | return Effect.fail( |
| 919 | new GitCommandError({ | 964 | new GitCommandError({ |
| 920 | operation: input.operation, | 965 | operation: input.operation, |
| 921 | command: `git ${input.args.join(" ")}`, | 966 | command: `git ${input.args.join(" ")}`, |
| 922 | cwd: input.cwd, | 967 | cwd: input.cwd, |
| 923 | detail: "simulated fetch timeout", | 968 | detail: "Unexpected git command in refresh failure cooldown test.", |
| 924 | }), | 969 | }), |
| 925 | ); | 970 | ); |
| 926 | } | 971 | }); |
| 927 | if (input.operation === "GitCore.statusDetails.status") { | | |
| 928 | return ok("# branch.head main\n# branch.upstream origin/main\n# branch.ab +0 -0\n"); | | |
| 929 | } | | |
| 930 | if ( | | |
| 931 | input.operation === "GitCore.statusDetails.unstagedNumstat" || | | |
| 932 | input.operation === "GitCore.statusDetails.stagedNumstat" | | |
| 933 | ) { | | |
| 934 | return ok(); | | |
| 935 | } | | |
| 936 | if (input.operation === "GitCore.statusDetails.defaultRef") { | | |
| 937 | return ok("refs/remotes/origin/main\n"); | | |
| 938 | } | | |
| 939 | return Effect.fail( | | |
| 940 | new GitCommandError({ | | |
| 941 | operation: input.operation, | | |
| 942 | command: `git ${input.args.join(" ")}`, | | |
| 943 | cwd: input.cwd, | | |
| 944 | detail: "Unexpected git command in refresh failure cooldown test.", | | |
| 945 | }), | | |
| 946 | ); | | |
| 947 | }); | | |
| 948 | | 972 | |
| 949 | yield* core.statusDetails("/repo/worktrees/main"); | 973 | yield* core.statusDetails("/repo/worktrees/main"); |
| 950 | yield* core.statusDetails("/repo/worktrees/pr-123"); | 974 | yield* core.statusDetails("/repo/worktrees/pr-123"); |
| 951 | expect(fetchCount).toBe(1); | 975 | expect(fetchCount).toBe(1); |
| 952 | }), | 976 | }), |
| 953 | ); | 977 | ); |
| 954 | | 978 | |
| 955 | it.effect("throws when branch does not exist", () => | 979 | it.effect("throws when branch does not exist", () => |
| ... | @@ -1047,7 +1071,6 @@ it.layer(TestLayer)("git integration", (it) => { | ... | @@ -1047,7 +1071,6 @@ it.layer(TestLayer)("git integration", (it) => { |
| 1047 | "--quiet", | 1071 | "--quiet", |
| 1048 | "--no-tags", | 1072 | "--no-tags", |
| 1049 | remoteName, | 1073 | remoteName, |
| 1050 | `+refs/heads/${featureBranch}:refs/remotes/${remoteName}/${featureBranch}`, | | |
| 1051 | ]); | 1074 | ]); |
| 1052 | }), | 1075 | }), |
| 1053 | ); | 1076 | ); |