# Resolve convert-sha256 positionals against unset fields

`4562077`→[main](/content/gh/entireio/git-sync/commits/main/index.html)·

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

- cmd/git-sync

- Mconvert_sha256.go+23/-8

- Aconvert_sha256_test.go+84

```
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
