Skip to content

Commit e765697

Browse files
authored
Support binary diffs (#887)
Patches can contain non UTF-8 characters (who knew). Since we JSON-encode the patch as a string field, we lose that encoding over the wire to Sourcegraph. This PR adds support for a byte slice encoded (so base64 over the wire) mode from Sourcegraph 4.4 onwards, which retains original encoding.
1 parent fa50cb7 commit e765697

25 files changed

+215
-321
lines changed

.tool-versions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
golang 1.18.1
1+
golang 1.19.3
22
shfmt 3.2.0
33
shellcheck 0.7.1

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ All notable changes to `src-cli` are documented in this file.
1313

1414
### Added
1515

16+
### Changed
17+
18+
### Fixed
19+
20+
### Removed
21+
22+
## 4.2.1
23+
24+
### Added
25+
1626
- Batch specs being run locally with `src batch preview` or `src batch apply` can now be run with the `-run-as-root` flag, which will run all step containers as root instead of the default user for the image. This is off by default. [#886](https://github.com/sourcegraph/src-cli/pull/886)
1727

1828
### Changed
@@ -21,7 +31,7 @@ All notable changes to `src-cli` are documented in this file.
2131

2232
### Fixed
2333

24-
### Removed
34+
- Batch changes: Git patches are now binary encoded instead of UTF-8 over the wire, fixing support for non-UTF-8 files. [#887](https://github.com/sourcegraph/src-cli/pull/887)
2535

2636
## 4.2.0
2737

cmd/src/batch_apply.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import (
55
"flag"
66
"fmt"
77

8-
"github.com/sourcegraph/src-cli/internal/batches/ui"
98
"github.com/sourcegraph/src-cli/internal/cmderrors"
10-
11-
"github.com/sourcegraph/sourcegraph/lib/output"
129
)
1310

1411
func init() {
@@ -47,15 +44,7 @@ Examples:
4744
ctx, cancel := contextCancelOnInterrupt(context.Background())
4845
defer cancel()
4946

50-
var execUI ui.ExecUI
51-
if flags.textOnly {
52-
execUI = &ui.JSONLines{}
53-
} else {
54-
out := output.NewOutput(flagSet.Output(), output.OutputOpts{Verbose: *verbose})
55-
execUI = &ui.TUI{Out: out}
56-
}
57-
58-
if err = executeBatchSpec(ctx, execUI, executeBatchSpecOpts{
47+
if err = executeBatchSpec(ctx, executeBatchSpecOpts{
5948
flags: flags,
6049
client: cfg.apiClient(flags.api, flagSet.Output()),
6150
file: file,

cmd/src/batch_common.go

Lines changed: 60 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/mattn/go-isatty"
1919

2020
"github.com/sourcegraph/sourcegraph/lib/errors"
21+
"github.com/sourcegraph/sourcegraph/lib/output"
2122

2223
batcheslib "github.com/sourcegraph/sourcegraph/lib/batches"
2324
"github.com/sourcegraph/sourcegraph/lib/batches/template"
@@ -253,20 +254,38 @@ type executeBatchSpecOpts struct {
253254
// executeBatchSpec performs all the steps required to upload the batch spec to
254255
// Sourcegraph, including execution as needed and applying the resulting batch
255256
// spec if specified.
256-
func executeBatchSpec(ctx context.Context, ui ui.ExecUI, opts executeBatchSpecOpts) (err error) {
257+
func executeBatchSpec(ctx context.Context, opts executeBatchSpecOpts) (err error) {
258+
var execUI ui.ExecUI
259+
if opts.flags.textOnly {
260+
execUI = &ui.JSONLines{}
261+
} else {
262+
out := output.NewOutput(os.Stderr, output.OutputOpts{Verbose: *verbose})
263+
execUI = &ui.TUI{Out: out}
264+
}
265+
257266
defer func() {
258267
if err != nil {
259-
ui.ExecutionError(err)
268+
execUI.ExecutionError(err)
260269
}
261270
}()
262271

263272
svc := service.New(&service.Opts{
264273
Client: opts.client,
265274
})
266275

276+
ffs, err := svc.DetermineFeatureFlags(ctx)
277+
if err != nil {
278+
return err
279+
}
280+
281+
// Once we know about feature flags, reconfigure the UI if needed.
282+
if opts.flags.textOnly && ffs.BinaryDiffs {
283+
execUI = &ui.JSONLines{BinaryDiffs: true}
284+
}
285+
267286
imageCache := docker.NewImageCache()
268287

269-
if err := validateSourcegraphVersionConstraint(ctx, svc); err != nil {
288+
if err := validateSourcegraphVersionConstraint(ctx, ffs); err != nil {
270289
return err
271290
}
272291

@@ -309,45 +328,45 @@ func executeBatchSpec(ctx context.Context, ui ui.ExecUI, opts executeBatchSpecOp
309328
}
310329

311330
// Parse flags and build up our service and executor options.
312-
ui.ParsingBatchSpec()
331+
execUI.ParsingBatchSpec()
313332
batchSpec, batchSpecDir, rawSpec, err := parseBatchSpec(ctx, opts.file, svc)
314333
if err != nil {
315334
var multiErr errors.MultiError
316335
if errors.As(err, &multiErr) {
317-
ui.ParsingBatchSpecFailure(multiErr)
336+
execUI.ParsingBatchSpecFailure(multiErr)
318337
return cmderrors.ExitCode(2, nil)
319338
} else {
320339
// This shouldn't happen; let's just punt and let the normal
321340
// rendering occur.
322341
return err
323342
}
324343
}
325-
ui.ParsingBatchSpecSuccess()
344+
execUI.ParsingBatchSpecSuccess()
326345

327-
ui.ResolvingNamespace()
346+
execUI.ResolvingNamespace()
328347
namespace, err := svc.ResolveNamespace(ctx, opts.flags.namespace)
329348
if err != nil {
330349
return err
331350
}
332-
ui.ResolvingNamespaceSuccess(namespace.ID)
351+
execUI.ResolvingNamespaceSuccess(namespace.ID)
333352

334353
var workspaceCreator workspace.Creator
335354

336355
if len(batchSpec.Steps) > 0 {
337-
ui.PreparingContainerImages()
356+
execUI.PreparingContainerImages()
338357
images, err := svc.EnsureDockerImages(
339358
ctx,
340359
imageCache,
341360
batchSpec.Steps,
342361
parallelism,
343-
ui.PreparingContainerImagesProgress,
362+
execUI.PreparingContainerImagesProgress,
344363
)
345364
if err != nil {
346365
return err
347366
}
348-
ui.PreparingContainerImagesSuccess()
367+
execUI.PreparingContainerImagesSuccess()
349368

350-
ui.DeterminingWorkspaceCreatorType()
369+
execUI.DeterminingWorkspaceCreatorType()
351370
var typ workspace.CreatorType
352371
workspaceCreator, typ = workspace.NewCreator(ctx, opts.flags.workspace, opts.flags.cacheDir, opts.flags.tempDir, images)
353372
if typ == workspace.CreatorTypeVolume {
@@ -357,21 +376,21 @@ func executeBatchSpec(ctx context.Context, ui ui.ExecUI, opts executeBatchSpecOp
357376
return err
358377
}
359378
}
360-
ui.DeterminingWorkspaceCreatorTypeSuccess(typ)
379+
execUI.DeterminingWorkspaceCreatorTypeSuccess(typ)
361380
}
362381

363-
ui.DeterminingWorkspaces()
382+
execUI.DeterminingWorkspaces()
364383
workspaces, repos, err := svc.ResolveWorkspacesForBatchSpec(ctx, batchSpec, opts.flags.allowUnsupported, opts.flags.allowIgnored)
365384
if err != nil {
366385
if repoSet, ok := err.(batches.UnsupportedRepoSet); ok {
367-
ui.DeterminingWorkspacesSuccess(len(workspaces), len(repos), repoSet, nil)
386+
execUI.DeterminingWorkspacesSuccess(len(workspaces), len(repos), repoSet, nil)
368387
} else if repoSet, ok := err.(batches.IgnoredRepoSet); ok {
369-
ui.DeterminingWorkspacesSuccess(len(workspaces), len(repos), nil, repoSet)
388+
execUI.DeterminingWorkspacesSuccess(len(workspaces), len(repos), nil, repoSet)
370389
} else {
371390
return errors.Wrap(err, "resolving repositories")
372391
}
373392
} else {
374-
ui.DeterminingWorkspacesSuccess(len(workspaces), len(repos), nil, nil)
393+
execUI.DeterminingWorkspacesSuccess(len(workspaces), len(repos), nil, nil)
375394
}
376395

377396
archiveRegistry := repozip.NewArchiveRegistry(opts.client, opts.flags.cacheDir, opts.flags.cleanArchives)
@@ -389,14 +408,16 @@ func executeBatchSpec(ctx context.Context, ui ui.ExecUI, opts executeBatchSpecOp
389408
TempDir: opts.flags.tempDir,
390409
GlobalEnv: os.Environ(),
391410
ForceRoot: opts.flags.runAsRoot,
411+
BinaryDiffs: ffs.BinaryDiffs,
392412
},
393-
Logger: logManager,
394-
Cache: executor.NewDiskCache(opts.flags.cacheDir),
395-
GlobalEnv: os.Environ(),
413+
Logger: logManager,
414+
Cache: executor.NewDiskCache(opts.flags.cacheDir),
415+
BinaryDiffs: ffs.BinaryDiffs,
416+
GlobalEnv: os.Environ(),
396417
},
397418
)
398419

399-
ui.CheckingCache()
420+
execUI.CheckingCache()
400421
tasks := svc.BuildTasks(
401422
&template.BatchChangeAttributes{
402423
Name: batchSpec.Name,
@@ -421,9 +442,9 @@ func executeBatchSpec(ctx context.Context, ui ui.ExecUI, opts executeBatchSpecOp
421442
return err
422443
}
423444
}
424-
ui.CheckingCacheSuccess(len(specs), len(uncachedTasks))
445+
execUI.CheckingCacheSuccess(len(specs), len(uncachedTasks))
425446

426-
taskExecUI := ui.ExecutingTasks(*verbose, parallelism)
447+
taskExecUI := execUI.ExecutingTasks(*verbose, parallelism)
427448
freshSpecs, logFiles, execErr := coord.ExecuteAndBuildSpecs(ctx, batchSpec, uncachedTasks, taskExecUI)
428449
// Add external changeset specs.
429450
importedSpecs, importErr := svc.CreateImportChangesetSpecs(ctx, batchSpec)
@@ -440,7 +461,7 @@ func executeBatchSpec(ctx context.Context, ui ui.ExecUI, opts executeBatchSpecOp
440461
if err == nil {
441462
taskExecUI.Success()
442463
} else {
443-
ui.ExecutingTasksSkippingErrors(err)
464+
execUI.ExecutingTasksSkippingErrors(err)
444465
}
445466
} else {
446467
if err != nil {
@@ -450,7 +471,7 @@ func executeBatchSpec(ctx context.Context, ui ui.ExecUI, opts executeBatchSpecOp
450471
}
451472

452473
if len(logFiles) > 0 && opts.flags.keepLogs {
453-
ui.LogFilesKept(logFiles)
474+
execUI.LogFilesKept(logFiles)
454475
}
455476

456477
specs = append(specs, freshSpecs...)
@@ -464,29 +485,29 @@ func executeBatchSpec(ctx context.Context, ui ui.ExecUI, opts executeBatchSpecOp
464485
ids := make([]graphql.ChangesetSpecID, len(specs))
465486

466487
if len(specs) > 0 {
467-
ui.UploadingChangesetSpecs(len(specs))
488+
execUI.UploadingChangesetSpecs(len(specs))
468489

469490
for i, spec := range specs {
470491
id, err := svc.CreateChangesetSpec(ctx, spec)
471492
if err != nil {
472493
return err
473494
}
474495
ids[i] = id
475-
ui.UploadingChangesetSpecsProgress(i+1, len(specs))
496+
execUI.UploadingChangesetSpecsProgress(i+1, len(specs))
476497
}
477498

478-
ui.UploadingChangesetSpecsSuccess(ids)
499+
execUI.UploadingChangesetSpecsSuccess(ids)
479500
} else if len(repos) == 0 {
480-
ui.NoChangesetSpecs()
501+
execUI.NoChangesetSpecs()
481502
}
482503

483-
ui.CreatingBatchSpec()
504+
execUI.CreatingBatchSpec()
484505
id, url, err := svc.CreateBatchSpec(ctx, namespace.ID, rawSpec, ids)
485506
if err != nil {
486-
return ui.CreatingBatchSpecError(err)
507+
return execUI.CreatingBatchSpecError(err)
487508
}
488509
previewURL := cfg.Endpoint + url
489-
ui.CreatingBatchSpecSuccess(previewURL)
510+
execUI.CreatingBatchSpecSuccess(previewURL)
490511

491512
hasWorkspaceFiles := false
492513
for _, step := range batchSpec.Steps {
@@ -496,26 +517,26 @@ func executeBatchSpec(ctx context.Context, ui ui.ExecUI, opts executeBatchSpecOp
496517
}
497518
}
498519
if hasWorkspaceFiles {
499-
ui.UploadingWorkspaceFiles()
520+
execUI.UploadingWorkspaceFiles()
500521
if err = svc.UploadBatchSpecWorkspaceFiles(ctx, batchSpecDir, string(id), batchSpec.Steps); err != nil {
501522
// Since failing to upload workspace files should not stop processing, just warn
502-
ui.UploadingWorkspaceFilesWarning(errors.Wrap(err, "uploading workspace files"))
523+
execUI.UploadingWorkspaceFilesWarning(errors.Wrap(err, "uploading workspace files"))
503524
} else {
504-
ui.UploadingWorkspaceFilesSuccess()
525+
execUI.UploadingWorkspaceFilesSuccess()
505526
}
506527
}
507528

508529
if !opts.applyBatchSpec {
509-
ui.PreviewBatchSpec(previewURL)
530+
execUI.PreviewBatchSpec(previewURL)
510531
return
511532
}
512533

513-
ui.ApplyingBatchSpec()
534+
execUI.ApplyingBatchSpec()
514535
batch, err := svc.ApplyBatchChange(ctx, id)
515536
if err != nil {
516537
return err
517538
}
518-
ui.ApplyingBatchSpecSuccess(cfg.Endpoint + batch.URL)
539+
execUI.ApplyingBatchSpecSuccess(cfg.Endpoint + batch.URL)
519540

520541
return nil
521542
}
@@ -617,11 +638,7 @@ func getBatchParallelism(ctx context.Context, flag int) (int, error) {
617638
return docker.NCPU(ctx)
618639
}
619640

620-
func validateSourcegraphVersionConstraint(ctx context.Context, svc *service.Service) error {
621-
ffs, err := svc.DetermineFeatureFlags(ctx)
622-
if err != nil {
623-
return err
624-
}
641+
func validateSourcegraphVersionConstraint(ctx context.Context, ffs *batches.FeatureFlags) error {
625642
if ffs.Sourcegraph40 {
626643
return nil
627644
}

cmd/src/batch_exec.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type executorModeFlags struct {
3737
tempDir string
3838
repoDir string
3939
workspaceFilesDir string
40+
binaryDiffs bool
4041
}
4142

4243
func newExecutorModeFlags(flagSet *flag.FlagSet) (f *executorModeFlags) {
@@ -47,6 +48,7 @@ func newExecutorModeFlags(flagSet *flag.FlagSet) (f *executorModeFlags) {
4748
flagSet.StringVar(&f.tempDir, "tmp", "", "Directory for storing temporary data.")
4849
flagSet.StringVar(&f.repoDir, "repo", "", "Path of the checked out repo on disk.")
4950
flagSet.StringVar(&f.workspaceFilesDir, "workspaceFiles", "", "Path of workspace files on disk.")
51+
flagSet.BoolVar(&f.binaryDiffs, "binaryDiffs", false, "Whether to encode diffs as base64.")
5052

5153
return f
5254
}
@@ -121,7 +123,7 @@ Examples:
121123
}
122124

123125
func executeBatchSpecInWorkspaces(ctx context.Context, flags *executorModeFlags) (err error) {
124-
ui := &ui.JSONLines{}
126+
ui := &ui.JSONLines{BinaryDiffs: flags.binaryDiffs}
125127

126128
// Ensure the temp dir exists.
127129
tempDir := flags.tempDir
@@ -211,6 +213,7 @@ func executeBatchSpecInWorkspaces(ctx context.Context, flags *executorModeFlags)
211213
RepoArchive: &repozip.NoopArchive{},
212214
UI: taskExecUI.StepsExecutionUI(task),
213215
ForceRoot: !flags.runAsImageUser,
216+
BinaryDiffs: flags.binaryDiffs,
214217
}
215218
results, err := executor.RunSteps(ctx, opts)
216219

cmd/src/batch_new.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ Examples:
4848
Client: cfg.apiClient(apiFlags, flagSet.Output()),
4949
})
5050

51-
if err := validateSourcegraphVersionConstraint(ctx, svc); err != nil {
51+
ffs, err := svc.DetermineFeatureFlags(ctx)
52+
if err != nil {
53+
return err
54+
}
55+
56+
if err := validateSourcegraphVersionConstraint(ctx, ffs); err != nil {
5257
return err
5358
}
5459

cmd/src/batch_preview.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import (
55
"flag"
66
"fmt"
77

8-
"github.com/sourcegraph/src-cli/internal/batches/ui"
98
"github.com/sourcegraph/src-cli/internal/cmderrors"
10-
11-
"github.com/sourcegraph/sourcegraph/lib/output"
129
)
1310

1411
func init() {
@@ -45,15 +42,7 @@ Examples:
4542
ctx, cancel := contextCancelOnInterrupt(context.Background())
4643
defer cancel()
4744

48-
var execUI ui.ExecUI
49-
if flags.textOnly {
50-
execUI = &ui.JSONLines{}
51-
} else {
52-
out := output.NewOutput(flagSet.Output(), output.OutputOpts{Verbose: *verbose})
53-
execUI = &ui.TUI{Out: out}
54-
}
55-
56-
if err = executeBatchSpec(ctx, execUI, executeBatchSpecOpts{
45+
if err = executeBatchSpec(ctx, executeBatchSpecOpts{
5746
flags: flags,
5847
client: cfg.apiClient(flags.api, flagSet.Output()),
5948
file: file,

0 commit comments

Comments
 (0)