Resolve convert-sha256 positionals against unset fields · Entire

Resolve convert-sha256 positionals against unset fields

4562077→main·

nodo·1mo ago·2 files·+107 added/-8 removed

The previous code assigned args[0] to SourceURL and args[1] to TargetDir unconditionally, so --source-url <url> <dir> left TargetDir empty (len(args) == 1, so args[1] was never read) and failed the "requires a source URL and a target directory" check.

Consume positionals left-to-right against fields the user has not yet supplied via flags. The four flag/positional shapes (zero/one/two flags) now all yield the correct assignment. Extracted into resolveConvertSHA256Args for a unit test that covers every shape including the regression case.

Sessions

Transcript data is unavailable for this checkpoint.

Changes

2

41 unmodified lines

42
43
44
45
46
47
48
49
50
51
52
45
46
47
48
49
44 unmodified lines

94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

41 unmodified lines

SilenceUsage:  true,
        RunE: func(cmd *cobra.Command, args []string) error {
            req.ProtocolMode = gitsync.ProtocolMode(protocolVal)
            if req.SourceURL == "" && len(args) > 0 {
                req.SourceURL = args[0]
            }
            if req.TargetDir == "" && len(args) > 1 {
                req.TargetDir = args[1]
            }
            if req.SourceURL == "" || req.TargetDir == "" {
                return errors.New("convert-sha256 requires a source URL and a target directory")
            }
            if err := resolveConvertSHA256Args(&req, args); err != nil {
                return err
            }

result, err := sha256convert.Run(cmd.Context(), req)
41 unmodified lines

return cmd
    }

// resolveConvertSHA256Args consumes positional args left-to-right,
// skipping fields the user already supplied via flags. Without that
// rule, `--source-url <url> <dir>` would look like one positional and
// land in SourceURL — leaving TargetDir empty even though the user
// gave both. The two-flags-no-positionals and zero-flags-two-positionals
// shapes also work, as do the symmetric --target-dir + positional URL.
func resolveConvertSHA256Args(req *sha256convert.Request, args []string) error {
    positional := args
    if req.SourceURL == "" && len(positional) > 0 {
        req.SourceURL = positional[0]
        positional = positional[1:]
    }
    if req.TargetDir == "" && len(positional) > 0 {
        req.TargetDir = positional[0]
    }
    if req.SourceURL == "" || req.TargetDir == "" {
        return errors.New("convert-sha256 requires a source URL and a target directory")
    }
    return nil
}

Mcmd/git-sync/convert_sha256.go+23/-8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

package main

import (
    "strings"
    "testing"

"entire.io/entire/git-sync/cmd/git-sync/internal/sha256convert"
)

func TestResolveConvertSHA256Args(t *testing.T) {
    const url = "http://example.invalid/repo.git"
    const dir = "/tmp/out"

tests := []struct {
        name    string
        req     sha256convert.Request
        args    []string
        wantURL string
        wantDir string
        wantErr string
    }{
        {
            name:    "both positionals",
            args:    []string{url, dir},
            wantURL: url,
            wantDir: dir,
        },
        {
            name:    "url flag plus positional dir — the reported bug",
            req:     sha256convert.Request{SourceURL: url},
            args:    []string{dir},
            wantURL: url,
            wantDir: dir,
        },
        {
            name:    "dir flag plus positional url",
            req:     sha256convert.Request{TargetDir: dir},
            args:    []string{url},
            wantURL: url,
            wantDir: dir,
        },
        {
            name:    "both flags, no positionals",
            req:     sha256convert.Request{SourceURL: url, TargetDir: dir},
            args:    nil,
            wantURL: url,
            wantDir: dir,
        },
        {
            name:    "missing dir",
            req:     sha256convert.Request{SourceURL: url},
            args:    nil,
            wantErr: "requires a source URL and a target directory",
        },
        {
            name:    "missing both",
            args:    nil,
            wantErr: "requires a source URL and a target directory",
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            req := tt.req
            err := resolveConvertSHA256Args(&req, tt.args)
            switch {
            case tt.wantErr == "" && err != nil:
                t.Fatalf("unexpected error: %v", err)
            case tt.wantErr != "" && err == nil:
                t.Fatalf("expected error containing %q, got nil", tt.wantErr)
            case tt.wantErr != "" && !strings.Contains(err.Error(), tt.wantErr):
                t.Fatalf("error %q does not contain %q", err.Error(), tt.wantErr)
            }
            if tt.wantErr != "" {
                return
            }
            if req.SourceURL != tt.wantURL {
                t.Errorf("SourceURL: got %q, want %q", req.SourceURL, tt.wantURL)
            }
            if req.TargetDir != tt.wantDir {
                t.Errorf("TargetDir: got %q, want %q", req.TargetDir, tt.wantDir)
            }
        }
    }
}

Acmd/git-sync/convert_sha256_test.go+84