<commit_before>import * as chai from 'chai'
const expect = chai.expect

import * as path from 'path'

const fs = require('fs-extra')
const temp = require('temp').track()

import { Repository } from '../../src/models/repository'
import { LocalGitOperations, BranchType } from '../../src/lib/local-git-operations'
import { GitDiff } from '../../src/lib/git/git-diff'
import { FileStatus, WorkingDirectoryFileChange } from '../../src/models/status'
import { DiffSelectionType, DiffSelection } from '../../src/models/diff'
import { setupFixtureRepository, setupEmptyRepository } from '../fixture-helper'

import { GitProcess } from 'git-kitchen-sink'

describe('LocalGitOperations', () => {
  let repository: Repository | null = null

  beforeEach(() => {
    const testRepoPath = setupFixtureRepository('test-repo')
    repository = new Repository(testRepoPath, -1, null)
  })

  after(() => {
    temp.cleanupSync()
  })

  describe('status', () => {
    it('parses changed files', async () => {
      fs.writeFileSync(path.join(repository!.path, 'README.md'), 'Hi world\n')

      const status = await LocalGitOperations.getStatus(repository!)
      const files = status.workingDirectory.files
      expect(files.length).to.equal(1)

      const file = files[0]
      expect(file.path).to.equal('README.md')
      expect(file.status).to.equal(FileStatus.Modified)
    })

    it('returns an empty array when there are no changes', async () => {
      const status = await LocalGitOperations.getStatus(repository!)
      const files = status.workingDirectory.files
      expect(files.length).to.equal(0)
    })

    it('reflects renames', async () => {

      const repo = await setupEmptyRepository()

      fs.writeFileSync(path.join(repo.path, 'foo'), 'foo\n')

      await GitProcess.exec([ 'add', 'foo' ], repo.path)
      await GitProcess.exec([ 'commit', '-m', 'Initial commit' ], repo.path)
      await GitProcess.exec([ 'mv', 'foo', 'bar' ], repo.path)

      const status = await LocalGitOperations.getStatus(repo)
      const files = status.workingDirectory.files

      expect(files.length).to.equal(1)
      expect(files[0].status).to.equal(FileStatus.Renamed)
      expect(files[0].oldPath).to.equal('foo')
      expect(files[0].path).to.equal('bar')
    })
  })

  describe('committing', () => {
    it('commits the given files', async () => {
      fs.writeFileSync(path.join(repository!.path, 'README.md'), 'Hi world\n')

      let status = await LocalGitOperations.getStatus(repository!)
      let files = status.workingDirectory.files
      expect(files.length).to.equal(1)

      await LocalGitOperations.createCommit(repository!, 'Special commit', '', files)

      status = await LocalGitOperations.getStatus(repository!)
      files = status.workingDirectory.files
      expect(files.length).to.equal(0)

      const commits = await LocalGitOperations.getHistory(repository!, 'HEAD', 100)
      expect(commits.length).to.equal(6)
      expect(commits[0].summary).to.equal('Special commit')
    })

    it('can commit renames', async () => {

      const repo = await setupEmptyRepository()

      fs.writeFileSync(path.join(repo.path, 'foo'), 'foo\n')

      await GitProcess.exec([ 'add', 'foo' ], repo.path)
      await GitProcess.exec([ 'commit', '-m', 'Initial commit' ], repo.path)
      await GitProcess.exec([ 'mv', 'foo', 'bar' ], repo.path)

      const status = await LocalGitOperations.getStatus(repo)
      const files = status.workingDirectory.files

      expect(files.length).to.equal(1)

      await LocalGitOperations.createCommit(repo, 'renamed a file', '', [ files[0].withIncludeAll(true) ])

      const statusAfter = await LocalGitOperations.getStatus(repo)

      expect(statusAfter.workingDirectory.files.length).to.equal(0)
    })
  })

  describe('partial commits', () => {

    beforeEach(() => {
      const testRepoPath = setupFixtureRepository('repo-with-changes')
      repository = new Repository(testRepoPath, -1, null)
    })

    it('can commit some lines from new file', async () => {
      const previousTip = (await LocalGitOperations.getHistory(repository!, 'HEAD', 1))[0]

      const newFileName = 'new-file.md'

      // select first five lines of file
      const selection = DiffSelection
        .fromInitialSelection(DiffSelectionType.None)
        .withRangeSelection(0, 5, true)

      const file = new WorkingDirectoryFileChange(newFileName, FileStatus.New, selection)

      // commit just this change, ignore everything else
      await LocalGitOperations.createCommit(repository!, 'title', '', [ file ])

      // verify that the HEAD of the repository has moved
      const newTip = (await LocalGitOperations.getHistory(repository!, 'HEAD', 1))[0]
      expect(newTip.sha).to.not.equal(previousTip.sha)
      expect(newTip.summary).to.equal('title')

      // verify that the contents of this new commit are just the new file
      const changedFiles = await LocalGitOperations.getChangedFiles(repository!, newTip.sha)
      expect(changedFiles.length).to.equal(1)
      expect(changedFiles[0].path).to.equal(newFileName)

      // verify that changes remain for this new file
      const status = await LocalGitOperations.getStatus(repository!)
      expect(status.workingDirectory.files.length).to.equal(4)

      // verify that the file is now tracked
      const fileChange = status.workingDirectory.files.find(f => f.path === newFileName)
      expect(fileChange).to.not.be.undefined
      expect(fileChange!.status).to.equal(FileStatus.Modified)
    })

    it('can commit second hunk from modified file', async () => {

      const previousTip = (await LocalGitOperations.getHistory(repository!, 'HEAD', 1))[0]

      const modifiedFile = 'modified-file.md'

      const unselectedFile = DiffSelection.fromInitialSelection(DiffSelectionType.None)
      const file = new WorkingDirectoryFileChange(modifiedFile, FileStatus.Modified, unselectedFile)

      const diff = await GitDiff.getWorkingDirectoryDiff(repository!, file)

      const selection = DiffSelection
        .fromInitialSelection(DiffSelectionType.All)
        .withRangeSelection(diff.hunks[0].unifiedDiffStart, diff.hunks[0].unifiedDiffEnd - diff.hunks[0].unifiedDiffStart, false)

      const updatedFile = file.withSelection(selection)

      // commit just this change, ignore everything else
      await LocalGitOperations.createCommit(repository!, 'title', '', [ updatedFile ])

      // verify that the HEAD of the repository has moved
      const newTip = (await LocalGitOperations.getHistory(repository!, 'HEAD', 1))[0]
      expect(newTip.sha).to.not.equal(previousTip.sha)
      expect(newTip.summary).to.equal('title')

      // verify that the contents of this new commit are just the modified file
      const changedFiles = await LocalGitOperations.getChangedFiles(repository!, newTip.sha)
      expect(changedFiles.length).to.equal(1)
      expect(changedFiles[0].path).to.equal(modifiedFile)

      // verify that changes remain for this modified file
      const status = await LocalGitOperations.getStatus(repository!)
      expect(status.workingDirectory.files.length).to.equal(4)

      // verify that the file is still marked as modified
      const fileChange = status.workingDirectory.files.find(f => f.path === modifiedFile)
      expect(fileChange).to.not.be.undefined
      expect(fileChange!.status).to.equal(FileStatus.Modified)
    })

    it('can commit multiple hunks from modified file', async () => {

      const previousTip = (await LocalGitOperations.getHistory(repository!, 'HEAD', 1))[0]

      const modifiedFile = 'modified-file.md'

      const unselectedFile = DiffSelection.fromInitialSelection(DiffSelectionType.None)
      const file = new WorkingDirectoryFileChange(modifiedFile, FileStatus.Modified, unselectedFile)

      const diff = await GitDiff.getWorkingDirectoryDiff(repository!, file)

      const selection = DiffSelection
        .fromInitialSelection(DiffSelectionType.All)
        .withRangeSelection(diff.hunks[1].unifiedDiffStart, diff.hunks[1].unifiedDiffEnd - diff.hunks[1].unifiedDiffStart, false)

      const updatedFile = new WorkingDirectoryFileChange(modifiedFile, FileStatus.Modified, selection)

      // commit just this change, ignore everything else<commit_msg>added test for single-line selection for partial commit<commit_after>import * as chai from 'chai'
const expect = chai.expect

import * as path from 'path'

const fs = require('fs-extra')
const temp = require('temp').track()

import { Repository } from '../../src/models/repository'
import { LocalGitOperations, BranchType } from '../../src/lib/local-git-operations'
import { GitDiff } from '../../src/lib/git/git-diff'
import { FileStatus, WorkingDirectoryFileChange } from '../../src/models/status'
import { DiffSelectionType, DiffSelection } from '../../src/models/diff'
import { setupFixtureRepository, setupEmptyRepository } from '../fixture-helper'

import { GitProcess } from 'git-kitchen-sink'

describe('LocalGitOperations', () => {
  let repository: Repository | null = null

  beforeEach(() => {
    const testRepoPath = setupFixtureRepository('test-repo')
    repository = new Repository(testRepoPath, -1, null)
  })

  after(() => {
    temp.cleanupSync()
  })

  describe('status', () => {
    it('parses changed files', async () => {
      fs.writeFileSync(path.join(repository!.path, 'README.md'), 'Hi world\n')

      const status = await LocalGitOperations.getStatus(repository!)
      const files = status.workingDirectory.files
      expect(files.length).to.equal(1)

      const file = files[0]
      expect(file.path).to.equal('README.md')
      expect(file.status).to.equal(FileStatus.Modified)
    })

    it('returns an empty array when there are no changes', async () => {
      const status = await LocalGitOperations.getStatus(repository!)
      const files = status.workingDirectory.files
      expect(files.length).to.equal(0)
    })

    it('reflects renames', async () => {

      const repo = await setupEmptyRepository()

      fs.writeFileSync(path.join(repo.path, 'foo'), 'foo\n')

      await GitProcess.exec([ 'add', 'foo' ], repo.path)
      await GitProcess.exec([ 'commit', '-m', 'Initial commit' ], repo.path)
      await GitProcess.exec([ 'mv', 'foo', 'bar' ], repo.path)

      const status = await LocalGitOperations.getStatus(repo)
      const files = status.workingDirectory.files

      expect(files.length).to.equal(1)
      expect(files[0].status).to.equal(FileStatus.Renamed)
      expect(files[0].oldPath).to.equal('foo')
      expect(files[0].path).to.equal('bar')
    })
  })

  describe('committing', () => {
    it('commits the given files', async () => {
      fs.writeFileSync(path.join(repository!.path, 'README.md'), 'Hi world\n')

      let status = await LocalGitOperations.getStatus(repository!)
      let files = status.workingDirectory.files
      expect(files.length).to.equal(1)

      await LocalGitOperations.createCommit(repository!, 'Special commit', '', files)

      status = await LocalGitOperations.getStatus(repository!)
      files = status.workingDirectory.files
      expect(files.length).to.equal(0)

      const commits = await LocalGitOperations.getHistory(repository!, 'HEAD', 100)
      expect(commits.length).to.equal(6)
      expect(commits[0].summary).to.equal('Special commit')
    })

    it('can commit renames', async () => {

      const repo = await setupEmptyRepository()

      fs.writeFileSync(path.join(repo.path, 'foo'), 'foo\n')

      await GitProcess.exec([ 'add', 'foo' ], repo.path)
      await GitProcess.exec([ 'commit', '-m', 'Initial commit' ], repo.path)
      await GitProcess.exec([ 'mv', 'foo', 'bar' ], repo.path)

      const status = await LocalGitOperations.getStatus(repo)
      const files = status.workingDirectory.files

      expect(files.length).to.equal(1)

      await LocalGitOperations.createCommit(repo, 'renamed a file', '', [ files[0].withIncludeAll(true) ])

      const statusAfter = await LocalGitOperations.getStatus(repo)

      expect(statusAfter.workingDirectory.files.length).to.equal(0)
    })
  })

  describe('partial commits', () => {

    beforeEach(() => {
      const testRepoPath = setupFixtureRepository('repo-with-changes')
      repository = new Repository(testRepoPath, -1, null)
    })

    it('can commit some lines from new file', async () => {
      const previousTip = (await LocalGitOperations.getHistory(repository!, 'HEAD', 1))[0]

      const newFileName = 'new-file.md'

      // select first five lines of file
      const selection = DiffSelection
        .fromInitialSelection(DiffSelectionType.None)
        .withRangeSelection(0, 5, true)

      const file = new WorkingDirectoryFileChange(newFileName, FileStatus.New, selection)

      // commit just this change, ignore everything else
      await LocalGitOperations.createCommit(repository!, 'title', '', [ file ])

      // verify that the HEAD of the repository has moved
      const newTip = (await LocalGitOperations.getHistory(repository!, 'HEAD', 1))[0]
      expect(newTip.sha).to.not.equal(previousTip.sha)
      expect(newTip.summary).to.equal('title')

      // verify that the contents of this new commit are just the new file
      const changedFiles = await LocalGitOperations.getChangedFiles(repository!, newTip.sha)
      expect(changedFiles.length).to.equal(1)
      expect(changedFiles[0].path).to.equal(newFileName)

      // verify that changes remain for this new file
      const status = await LocalGitOperations.getStatus(repository!)
      expect(status.workingDirectory.files.length).to.equal(4)

      // verify that the file is now tracked
      const fileChange = status.workingDirectory.files.find(f => f.path === newFileName)
      expect(fileChange).to.not.be.undefined
      expect(fileChange!.status).to.equal(FileStatus.Modified)
    })

    it('can commit second hunk from modified file', async () => {

      const previousTip = (await LocalGitOperations.getHistory(repository!, 'HEAD', 1))[0]

      const modifiedFile = 'modified-file.md'

      const unselectedFile = DiffSelection.fromInitialSelection(DiffSelectionType.None)
      const file = new WorkingDirectoryFileChange(modifiedFile, FileStatus.Modified, unselectedFile)

      const diff = await GitDiff.getWorkingDirectoryDiff(repository!, file)

      const selection = DiffSelection
        .fromInitialSelection(DiffSelectionType.All)
        .withRangeSelection(diff.hunks[0].unifiedDiffStart, diff.hunks[0].unifiedDiffEnd - diff.hunks[0].unifiedDiffStart, false)

      const updatedFile = file.withSelection(selection)

      // commit just this change, ignore everything else
      await LocalGitOperations.createCommit(repository!, 'title', '', [ updatedFile ])

      // verify that the HEAD of the repository has moved
      const newTip = (await LocalGitOperations.getHistory(repository!, 'HEAD', 1))[0]
      expect(newTip.sha).to.not.equal(previousTip.sha)
      expect(newTip.summary).to.equal('title')

      // verify that the contents of this new commit are just the modified file
      const changedFiles = await LocalGitOperations.getChangedFiles(repository!, newTip.sha)
      expect(changedFiles.length).to.equal(1)
      expect(changedFiles[0].path).to.equal(modifiedFile)

      // verify that changes remain for this modified file
      const status = await LocalGitOperations.getStatus(repository!)
      expect(status.workingDirectory.files.length).to.equal(4)

      // verify that the file is still marked as modified
      const fileChange = status.workingDirectory.files.find(f => f.path === modifiedFile)
      expect(fileChange).to.not.be.undefined
      expect(fileChange!.status).to.equal(FileStatus.Modified)
    })

    it('can commit single delete from modified file', async () => {
      const previousTip = (await LocalGitOperations.getHistory(repository!, 'HEAD', 1))[0]

      const fileName = 'modified-file.md'

      const unselectedFile = DiffSelection.fromInitialSelection(DiffSelectionType.None)
      const modifiedFile = new WorkingDirectoryFileChange(fileName, FileStatus.Modified, unselectedFile)

      const diff = await GitDiff.getWorkingDirectoryDiff(repository!, modifiedFile)

      const secondRemovedLine = diff.hunks[0].unifiedDiffStart + 5

      const selection = DiffSelection
        .fromInitialSelection(DiffSelectionType.None)
        .withRangeSelection(secondRemovedLine, 1, true)

      const file = new WorkingDirectoryFileChange(fileName, FileStatus.Modified, selection)

      // commit just this change, ignore everything else
      await LocalGitOperations.createCommit(repository!, 'title', '', [ file ])

      // verify that the HEAD of the repository has moved
      const newTip = (await LocalGitOperations.getHistory(repository!, 'HEAD', 1))[0]
      expect(newTip.sha).to.not.equal(previousTip.sha)
      expect(newTip.summary).to.equal('title')

      // verify that the contents of this new commit are just the modified file
      const changedFiles = await LocalGitOperations.getChangedFiles(repository!, newTip.sha)
      expect(changedFiles.length).to.equal(1)
      expect(changedFiles[0].path).to.equal(fileName)
    })

    it('can commit multiple hunks from modified file', async () => {

      const previousTip = (await LocalGitOperations.getHistory(repository!, 'HEAD', 1))[0]

      const modifiedFile = 'modified-file.md'

      const unselectedFile = DiffSelection.fromInitialSelection(DiffSelectionType.None)
      const file = new WorkingDirectoryFileChange(modifiedFile, FileStatus.Modified, unselectedFile)

      const diff = await GitDiff.getWorkingDirectoryDiff(repository!, file)

      const selection = DiffSelection
        .fromInitialSelection(DiffSelectionType.All)
        .withRangeSelection(diff.hunks[1].unifiedDiffStart, diff.hunks[1].unifiedDiffEnd - diff.hunks[1].unifiedDiffStart, false)

      const updatedFile = new WorkingDirectoryFileChange(modifiedFile, FileStatus.Modified, selection)

      // commit just this change, ignore everything else
{'fraction_non_alphanumeric': 0.12440482781530285, 'fraction_numerical': 0.0052596611670911305, 'mean_word_length': 3.6614193548387095, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 38, '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': '5702785', 'n_tokens_mistral': 5172, 'n_tokens_neox': 5015, 'n_words': 1154}