<commit_before>import * as React from 'react'
import { assertNever } from '../../lib/fatal-error'
import { Repository } from '../../models/repository'

import { RebaseStep, RebaseFlowState } from '../../models/rebase-flow-state'

import { ChooseBranchDialog } from './choose-branch'
import { ShowConflictedFilesDialog } from './show-conflicted-files-dialog'
import { Dispatcher } from '../dispatcher'
import { RebaseProgressDialog } from './progress-dialog'
import { BannerType } from '../../models/banner'
import { RebaseResult } from '../../lib/git'
import { RebaseConflictState } from '../../lib/app-state'
import { ConfirmAbortDialog } from './confirm-abort-dialog'
import { IRebaseProgress } from '../../models/progress'
import { WorkingDirectoryStatus } from '../../models/status'
import { clamp } from '../../lib/clamp'
import { timeout } from '../../lib/promise'

interface IRebaseFlowProps {
  /** Starting point for the rebase flow */
  readonly initialState: RebaseFlowState

  readonly repository: Repository
  readonly dispatcher: Dispatcher

  /** The current state of the working directory */
  readonly workingDirectory: WorkingDirectoryStatus

  /**
   * Snapshot of conflicts in the current repository
   *
   * Will be `null` while the rebase is in progress but has not encountered
   * conflicts.
   */
  readonly conflictState: RebaseConflictState | null

  readonly openFileInExternalEditor: (path: string) => void
  readonly resolvedExternalEditor: string | null
  readonly openRepositoryInShell: (repository: Repository) => void

  /**
   * Callback to fire to signal to the application that the rebase flow has
   * either ended in success or has been aborted and the flow can be closed.
   */
  readonly onFlowEnded: () => void
}

interface IRebaseFlowState {
  readonly step: RebaseFlowState

  readonly progress: {
    /** A numeric value between 0 and 1 representing the rebase progress */
    readonly value: number
    /** Track the current number of commits rebased across dialogs and states */
    readonly count: number
    /** Track the total number of commits to rebase across dialog and states */
    readonly total: number
    /** The commit summary associated with the current commit (if known) */
    readonly commitSummary?: string
  }
}

/** A component for initating a rebase of the current branch. */
export class RebaseFlow extends React.Component<
  IRebaseFlowProps,
  IRebaseFlowState
> {
  public constructor(props: IRebaseFlowProps) {
    super(props)

    this.state = {
      step: props.initialState,
      progress: {
        value: 0,
        count: 0,
        total: 0,
      },
    }
  }

  private showConflictedFiles = () => {
    const { workingDirectory, conflictState } = this.props

    if (conflictState === null) {
      log.error('[RebaseFlow] unable to detect conflicts for this repository')
      return
    }

    const { manualResolutions, targetBranch } = conflictState

    this.setState({
      step: {
        kind: RebaseStep.ShowConflicts,
        targetBranch,
        workingDirectory,
        manualResolutions,
      },
    })
  }

  private updateProgress = (progress: IRebaseProgress) => {
    // TODO: this can happen very quickly for a trivial rebase or an OS with
    // fast I/O - are we able to artificially slow this down so there's some
    // semblance of progress before it moves to "completed"?

    const { title, value, commitSummary, total, count } = progress
    log.info(`got progress: '${title}' '${value}' '${commitSummary}'`)

    this.setState({
      step: {
        kind: RebaseStep.ShowProgress,
      },
      progress: {
        value,
        commitSummary,
        count,
        total,
      },
    })
  }

  private moveToCompletedState = async () => {
    await timeout(1000)

    this.setState(
      {
        step: {
          kind: RebaseStep.Completed,
        },
      },
      () => this.props.onFlowEnded()
    )
  }

  private onStartRebase = (
    baseBranch: string,
    targetBranch: string,
    total: number
  ) => {
    if (this.state.step.kind !== RebaseStep.ChooseBranch) {
      throw new Error(`Invalid step to start rebase: ${this.state.step.kind}`)
    }

    const onDidMount = async () => {
      const result = await this.props.dispatcher.rebase(
        this.props.repository,
        baseBranch,
        targetBranch,
        {
          start: 0,
          total,
          progressCallback: this.updateProgress,
        }
      )

      if (result === RebaseResult.ConflictsEncountered) {
        await this.props.dispatcher.loadStatus(this.props.repository)

        this.showConflictedFiles()
      } else if (result === RebaseResult.CompletedWithoutError) {
        this.setState(
          prevState => {
            const { total } = prevState.progress
            return {
              step: {
                kind: RebaseStep.ShowProgress,
              },
              progress: {
                value: 1,
                count: total,
                total,
              },
            }
          },
          () => this.moveToCompletedState()
        )
      }
    }

    this.setState(() => ({
      step: {
        kind: RebaseStep.ShowProgress,
        onDidMount,
      },
      progress: {
        value: 0,
        count: 1,
        total,
      },
    }))
  }

  private onContinueRebase = async () => {
    if (this.state.step.kind !== RebaseStep.ShowConflicts) {
      throw new Error(
        `Invalid step to continue rebase rebase: ${this.state.step.kind}`
      )
    }

    const { conflictState } = this.props<commit_msg>leverage component lifecycle better to transiton to 'completed'<commit_after>import * as React from 'react'
import { assertNever } from '../../lib/fatal-error'
import { Repository } from '../../models/repository'

import { RebaseStep, RebaseFlowState } from '../../models/rebase-flow-state'

import { ChooseBranchDialog } from './choose-branch'
import { ShowConflictedFilesDialog } from './show-conflicted-files-dialog'
import { Dispatcher } from '../dispatcher'
import { RebaseProgressDialog } from './progress-dialog'
import { BannerType } from '../../models/banner'
import { RebaseResult } from '../../lib/git'
import { RebaseConflictState } from '../../lib/app-state'
import { ConfirmAbortDialog } from './confirm-abort-dialog'
import { IRebaseProgress } from '../../models/progress'
import { WorkingDirectoryStatus } from '../../models/status'
import { clamp } from '../../lib/clamp'
import { timeout } from '../../lib/promise'

interface IRebaseFlowProps {
  /** Starting point for the rebase flow */
  readonly initialState: RebaseFlowState

  readonly repository: Repository
  readonly dispatcher: Dispatcher

  /** The current state of the working directory */
  readonly workingDirectory: WorkingDirectoryStatus

  /**
   * Snapshot of conflicts in the current repository
   *
   * Will be `null` while the rebase is in progress but has not encountered
   * conflicts.
   */
  readonly conflictState: RebaseConflictState | null

  readonly openFileInExternalEditor: (path: string) => void
  readonly resolvedExternalEditor: string | null
  readonly openRepositoryInShell: (repository: Repository) => void

  /**
   * Callback to fire to signal to the application that the rebase flow has
   * either ended in success or has been aborted and the flow can be closed.
   */
  readonly onFlowEnded: () => void
}

interface IRebaseFlowState {
  readonly step: RebaseFlowState

  readonly progress: {
    /** A numeric value between 0 and 1 representing the rebase progress */
    readonly value: number
    /** Track the current number of commits rebased across dialogs and states */
    readonly count: number
    /** Track the total number of commits to rebase across dialog and states */
    readonly total: number
    /** The commit summary associated with the current commit (if known) */
    readonly commitSummary?: string
  }
}

/** A component for initating a rebase of the current branch. */
export class RebaseFlow extends React.Component<
  IRebaseFlowProps,
  IRebaseFlowState
> {
  public constructor(props: IRebaseFlowProps) {
    super(props)

    this.state = {
      step: props.initialState,
      progress: {
        value: 0,
        count: 0,
        total: 0,
      },
    }
  }

  public async componentDidUpdate(
    prevProps: IRebaseFlowProps,
    prevState: IRebaseFlowState
  ) {
    if (this.state.step.kind !== RebaseStep.ShowProgress) {
      return
    }

    if (this.state.progress.value !== 1) {
      return
    }

    // waiting before this transition to give the progress UI a chance to show
    // it reaches 100%
    await timeout(1000)

    this.setState(
      {
        step: {
          kind: RebaseStep.Completed,
        },
      },
      () => this.props.onFlowEnded()
    )
  }

  private showConflictedFiles = () => {
    const { workingDirectory, conflictState } = this.props

    if (conflictState === null) {
      log.error('[RebaseFlow] unable to detect conflicts for this repository')
      return
    }

    const { manualResolutions, targetBranch } = conflictState

    this.setState({
      step: {
        kind: RebaseStep.ShowConflicts,
        targetBranch,
        workingDirectory,
        manualResolutions,
      },
    })
  }

  private updateProgress = (progress: IRebaseProgress) => {
    const { title, value, commitSummary, total, count } = progress
    log.info(`got progress: '${title}' '${value}' '${commitSummary}'`)

    this.setState({
      progress: {
        value,
        commitSummary,
        count,
        total,
      },
    })
  }

  private moveToCompletedState = async () => {
    // this ensures the progress bar fills to 100%, and componentDidUpdate
    // handles the state transition after a period of time to ensure the UI
    // gracefully switches
    this.setState(prevState => {
      const { total } = prevState.progress
      return {
        progress: {
          value: 1,
          count: total,
          total,
        },
      }
    })
  }

  private onStartRebase = (
    baseBranch: string,
    targetBranch: string,
    total: number
  ) => {
    if (this.state.step.kind !== RebaseStep.ChooseBranch) {
      throw new Error(`Invalid step to start rebase: ${this.state.step.kind}`)
    }

    const onDidMount = async () => {
      const result = await this.props.dispatcher.rebase(
        this.props.repository,
        baseBranch,
        targetBranch,
        {
          start: 0,
          total,
          progressCallback: this.updateProgress,
        }
      )

      if (result === RebaseResult.ConflictsEncountered) {
        await this.props.dispatcher.loadStatus(this.props.repository)

        this.showConflictedFiles()
      } else if (result === RebaseResult.CompletedWithoutError) {
        this.moveToCompletedState()
      }
    }

    this.setState(() => ({
      step: {
        kind: RebaseStep.ShowProgress,
        onDidMount,
      },
      progress: {
        value: 0,
        count: 1,
        total,
      },
    }))
  }

  private onContinueRebase = async () => {
    if (this.state.step.kind !== RebaseStep.ShowConflicts) {
      throw new Error(
        `Invalid step to continue rebase rebase: ${this.state.step.kind}`
      )
    }

    const { conflictState } = this.props
{'fraction_non_alphanumeric': 0.10898965791567224, 'fraction_numerical': 0.002916998143728454, 'mean_word_length': 2.438905775075988, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 30, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5608079', 'n_tokens_mistral': 3182, 'n_tokens_neox': 3106, 'n_words': 910}