func Revlist()

in internal/git/gitpipe/revision.go [204:371]


func Revlist(
	ctx context.Context,
	repo *localrepo.Repo,
	revisions []string,
	options ...RevlistOption,
) RevisionIterator {
	var cfg revlistConfig
	for _, option := range options {
		option(&cfg)
	}

	resultChan := make(chan RevisionResult)
	go func() {
		defer close(resultChan)

		flags := []gitcmd.Option{
			gitcmd.Flag{Name: "--stdin"},
		}

		if cfg.objects {
			flags = append(flags,
				gitcmd.Flag{Name: "--in-commit-order"},
				gitcmd.Flag{Name: "--objects"},
				gitcmd.Flag{Name: "--object-names"},
			)
		}

		if cfg.blobLimit > 0 {
			flags = append(flags, gitcmd.Flag{
				Name: fmt.Sprintf("--filter=blob:limit=%d", cfg.blobLimit),
			})
		}

		if cfg.objectType != "" {
			flags = append(flags,
				gitcmd.Flag{Name: fmt.Sprintf("--filter=object:type=%s", cfg.objectType)},
				gitcmd.Flag{Name: "--filter-provided-objects"},
			)
		}

		switch cfg.order {
		case OrderNone:
			// Default order, nothing to do.
		case OrderTopo:
			flags = append(flags, gitcmd.Flag{Name: "--topo-order"})
		case OrderDate:
			flags = append(flags, gitcmd.Flag{Name: "--date-order"})
		}

		if cfg.reverse {
			flags = append(flags, gitcmd.Flag{Name: "--reverse"})
		}

		if cfg.maxParents > 0 {
			flags = append(flags, gitcmd.Flag{
				Name: fmt.Sprintf("--max-parents=%d", cfg.maxParents),
			},
			)
		}

		if cfg.disabledWalk {
			flags = append(flags, gitcmd.Flag{Name: "--no-walk"})
		}

		if cfg.firstParent {
			flags = append(flags, gitcmd.Flag{Name: "--first-parent"})
		}

		if !cfg.before.IsZero() {
			flags = append(flags, gitcmd.Flag{
				Name: fmt.Sprintf("--before=%s", cfg.before.String()),
			})
		}

		if !cfg.after.IsZero() {
			flags = append(flags, gitcmd.Flag{
				Name: fmt.Sprintf("--after=%s", cfg.after.String()),
			})
		}

		if len(cfg.author) > 0 {
			flags = append(flags, gitcmd.Flag{
				Name: fmt.Sprintf("--author=%s", string(cfg.author)),
			})
		}

		if cfg.regexIgnoreCase {
			flags = append(flags, gitcmd.Flag{Name: "--regexp-ignore-case"})
		}

		if len(cfg.commitMessagePatterns) > 0 {
			for _, pattern := range cfg.commitMessagePatterns {
				flags = append(flags, gitcmd.Flag{Name: fmt.Sprintf("--grep=%s", pattern)})
			}
		}

		if cfg.skip > 0 {
			flags = append(flags, gitcmd.Flag{
				Name: fmt.Sprintf("--skip=%d", cfg.skip),
			},
			)
		}

		var stderr strings.Builder
		revlist, err := repo.Exec(ctx,
			gitcmd.Command{
				Name:  "rev-list",
				Flags: flags,
			},
			gitcmd.WithStderr(&stderr),
			gitcmd.WithSetupStdout(),
			gitcmd.WithStdin(strings.NewReader(strings.Join(revisions, "\n"))),
		)
		if err != nil {
			sendRevisionResult(ctx, resultChan, RevisionResult{
				err: fmt.Errorf("rev-list: %w, stderr: %q", err, stderr.String()),
			})
			return
		}

		var execErrors error
		defer func() {
			if err := revlist.Wait(); err != nil {
				execErrors = errors.Join(execErrors, fmt.Errorf("rev-list pipeline command: %w, stderr: %q", err, stderr.String()))
			}

			if execErrors != nil {
				sendRevisionResult(ctx, resultChan, RevisionResult{
					err: execErrors,
				})
			}
		}()

		scanner := bufio.NewScanner(revlist)
		for scanner.Scan() {
			// We need to copy the line here because we'll hand it over to the caller
			// asynchronously, and the next call to `Scan()` will overwrite the buffer.
			line := make([]byte, len(scanner.Bytes()))
			copy(line, scanner.Bytes())

			oid, name, ok := bytes.Cut(line, []byte{' '})

			result := RevisionResult{
				OID: git.ObjectID(oid),
			}
			if ok && len(name) > 0 {
				result.ObjectName = name
			}

			if cfg.skipResult != nil && cfg.skipResult(&result) {
				continue
			}

			if isDone := sendRevisionResult(ctx, resultChan, result); isDone {
				return
			}
		}

		if err := scanner.Err(); err != nil {
			execErrors = errors.Join(execErrors, fmt.Errorf("scanning rev-list output: %w", err))
		}
	}()

	return &revisionIterator{
		ctx: ctx,
		ch:  resultChan,
	}
}