fix(clone): a CodeCommit region borrows saved key IDs; Clone runs in the background
An IAM SSH key ID logs in to every region of its AWS account, but a login was saved per host, so a repo in a region with no key ID of its own was refused. Clone now tries the key IDs saved for the other CodeCommit regions (each with its own key; the first ls-remote accepts, or the only one) and keeps that user in the clone's origin. Files → New → Clone repository steps aside for its progress chip as soon as the clone starts; a failure asking for a login reopens the form.
This commit is contained in:
@@ -7,10 +7,11 @@
|
||||
* folder's account with that account's own SSH key / HTTPS login — and opens
|
||||
* a workspace on the new folder. A 409 `ECREDENTIALS` opens a login row (sent
|
||||
* once, optionally remembered), `ESSHAUTH` points to Settings → Git.
|
||||
* A running clone shows `CloneProgress` and never holds the page: closing the
|
||||
* dialog only hides it behind a small progress chip (click to reopen), the
|
||||
* A clone runs in the background from the moment it starts: the dialog steps
|
||||
* aside for a small progress chip (`CloneProgress`; click to reopen), the
|
||||
* request keeps going, and its outcome arrives as a toast — a failure is kept
|
||||
* for the next open so a login row is still there to fill in.
|
||||
* for the next open, and one asking for a login reopens the dialog with its
|
||||
* login row.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -111,6 +112,9 @@ export function CloneHereDialog({
|
||||
setTarget(into);
|
||||
setError(null);
|
||||
setSshRefused(false);
|
||||
// Runs in the background by default: the dialog steps aside for the
|
||||
// progress chip, and comes back only if the clone needs a login.
|
||||
onClose();
|
||||
try {
|
||||
const result = await api.lanes.clone({
|
||||
url: u,
|
||||
@@ -156,6 +160,8 @@ export function CloneHereDialog({
|
||||
if (!openRef.current) {
|
||||
keepRef.current = true;
|
||||
toast.error(message);
|
||||
// A login to type: show the form again with its login row.
|
||||
if (e.code === "ECREDENTIALS") onReopen();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -632,14 +632,16 @@ describe("FileExplorer — New → Clone repository", () => {
|
||||
const dialog = await openClone(user);
|
||||
await user.type(within(dialog).getByLabelText("Repository URL"), "git@host:o/repo.git");
|
||||
await user.click(within(dialog).getByTestId("fx-clone-submit"));
|
||||
expect(await within(dialog).findByRole("progressbar")).toHaveAttribute("aria-valuenow", "45");
|
||||
await user.click(within(dialog).getByRole("button", { name: "Run in background" }));
|
||||
// In the background straight away: the chip carries the progress.
|
||||
expect(screen.queryByTestId("fx-clone-dialog")).toBeNull();
|
||||
const chip = await screen.findByTestId("fx-clone-chip");
|
||||
expect(within(chip).getByText(/repo/)).toBeInTheDocument();
|
||||
expect(await within(chip).findByRole("progressbar")).toHaveAttribute("aria-valuenow", "45");
|
||||
await user.click(chip);
|
||||
expect(screen.getByTestId("fx-clone-dialog")).toBeInTheDocument();
|
||||
await user.keyboard("{Escape}");
|
||||
const back = screen.getByTestId("fx-clone-dialog");
|
||||
expect(await within(back).findByRole("progressbar")).toHaveAttribute("aria-valuenow", "45");
|
||||
await user.click(within(back).getByRole("button", { name: "Run in background" }));
|
||||
expect(screen.queryByTestId("fx-clone-dialog")).toBeNull();
|
||||
finish({
|
||||
lane: { id: 9, title: "repo", cwd: "/w/one/repo" },
|
||||
created: true,
|
||||
@@ -649,7 +651,7 @@ describe("FileExplorer — New → Clone repository", () => {
|
||||
expect(screen.queryByTestId("fx-clone-chip")).toBeNull();
|
||||
});
|
||||
|
||||
it("toasts a failure that lands while hidden and keeps it for the next open", async () => {
|
||||
it("toasts a failure that lands while hidden, and reopens the form for a login", async () => {
|
||||
let fail: (e: unknown) => void = () => {};
|
||||
vi.mocked(api.lanes.clone).mockReturnValue(new Promise((_, r) => (fail = r)) as never);
|
||||
vi.mocked(api.lanes.cloneProgress).mockRejectedValue(new Error("404"));
|
||||
@@ -659,12 +661,12 @@ describe("FileExplorer — New → Clone repository", () => {
|
||||
const dialog = await openClone(user);
|
||||
await user.type(within(dialog).getByLabelText("Repository URL"), "https://git.example/o/r.git");
|
||||
await user.click(within(dialog).getByTestId("fx-clone-submit"));
|
||||
await user.keyboard("{Escape}");
|
||||
fail(
|
||||
Object.assign(new Error("login"), { code: "ECREDENTIALS", auth: { host: "git.example" } })
|
||||
);
|
||||
await waitFor(() => expect(toast.error).toHaveBeenCalled());
|
||||
const again = await openClone(user);
|
||||
// A login to type reopens the form by itself, its row ready.
|
||||
const again = await screen.findByTestId("fx-clone-dialog");
|
||||
expect(within(again).getByTestId("fx-clone-login")).toBeInTheDocument();
|
||||
expect(within(again).getByLabelText("Repository URL")).toHaveValue(
|
||||
"https://git.example/o/r.git"
|
||||
@@ -687,10 +689,11 @@ describe("FileExplorer — New → Clone repository", () => {
|
||||
const dialog = await openClone(user);
|
||||
await user.type(within(dialog).getByLabelText("Repository URL"), "https://git.example/o/r.git");
|
||||
await user.click(within(dialog).getByTestId("fx-clone-submit"));
|
||||
const login = await within(dialog).findByTestId("fx-clone-login");
|
||||
const reopened = await screen.findByTestId("fx-clone-dialog");
|
||||
const login = await within(reopened).findByTestId("fx-clone-login");
|
||||
await user.type(within(login).getByLabelText("Username"), "ann");
|
||||
await user.type(within(login).getByLabelText("Password or access token"), "tok");
|
||||
await user.click(within(dialog).getByTestId("fx-clone-submit"));
|
||||
await user.click(within(reopened).getByTestId("fx-clone-submit"));
|
||||
await waitFor(() =>
|
||||
expect(api.lanes.clone).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
|
||||
Reference in New Issue
Block a user