<commit_before># -*- encoding: utf-8

import wx
import wx.html
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin, ListCtrlSelectionManagerMix
import os
import sys

import Wizard
from DiffViewer import DiffViewer
from git import *
from util import *

MOD_DESCS = {
    FILE_ADDED       : 'added',
    FILE_MODIFIED    : 'modified',
    FILE_DELETED     : 'deleted',
    FILE_COPIED      : 'copied',
    FILE_RENAMED     : 'renamed',
    FILE_UNMERGED    : 'unmerged',
    FILE_TYPECHANGED : 'type changed',
    FILE_UNTRACKED   : 'untracked',
    FILE_BROKEN      : 'BROKEN',
    FILE_UNKNOWN     : 'UNKNOWN'
}

if sys.platform in ['win32', 'cygwin']:
    LABEL_STAGE = u"Stage >"
    LABEL_UNSTAGE = u"< Unstage"
    LABEL_DISCARD = u"× Discard"
else:
    LABEL_STAGE = u"Stage ⇒"
    LABEL_UNSTAGE = u"⇐ Unstage"
    LABEL_DISCARD = u"× Discard"

MENU_MERGE_FILE     = 20000
MENU_TAKE_LOCAL     = 20001
MENU_TAKE_REMOTE    = 20002

class FileList(wx.ListCtrl, ListCtrlAutoWidthMixin, ListCtrlSelectionManagerMix):
    def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.LC_REPORT | wx.LC_NO_HEADER):
        wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
        ListCtrlAutoWidthMixin.__init__(self)
        ListCtrlSelectionManagerMix.__init__(self)

        self.InsertColumn(0, "File")

    def GetSelections(self):
        return [ i for i in xrange(self.GetItemCount()) if self.GetItemState(i, wx.LIST_STATE_SELECTED) == wx.LIST_STATE_SELECTED ]

class IndexTab(wx.Panel):
    def __init__(self, mainWindow, parent, id):
        wx.Panel.__init__(self, parent, id)
        self.mainWindow = mainWindow
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self.sizer)
        
        # Splitter
        self.splitter = wx.SplitterWindow(self, -1, style=wx.SP_LIVE_UPDATE)
        self.sizer.Add(self.splitter, True, wx.EXPAND, wx.ALL)

        # Top panel
        self.topPanel = wx.Panel(self.splitter, -1)
        self.topSizer = wx.BoxSizer(wx.VERTICAL)
        self.topPanel.SetSizer(self.topSizer)

        # --- File lists ---
        self.listRow = wx.BoxSizer(wx.HORIZONTAL)
        self.topSizer.Add(self.listRow, 1, wx.EXPAND)

        # Unstaged changes
        self.unstagedBox = wx.StaticBox(self.topPanel, -1, "Unstaged changes")
        self.unstagedBoxSizer = wx.StaticBoxSizer(self.unstagedBox, wx.VERTICAL)
        self.listRow.Add(self.unstagedBoxSizer, 1, wx.EXPAND | wx.RIGHT, 10)

        self.unstagedList = FileList(self.topPanel, -1)
        self.unstagedBoxSizer.Add(self.unstagedList, 1, wx.EXPAND|wx.ALL, 0)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnUnstagedListSelect, self.unstagedList)
        self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnUnstagedRightClick, self.unstagedList)

        self.unstagedMenu = wx.Menu()
        self.unstagedMenu.Append(MENU_MERGE_FILE, "Merge file")
        self.unstagedMenu.Append(MENU_TAKE_LOCAL, "Take local version")
        self.unstagedMenu.Append(MENU_TAKE_REMOTE, "Take remote version")
        wx.EVT_MENU(self, MENU_MERGE_FILE, self.OnMergeFile)
        wx.EVT_MENU(self, MENU_TAKE_LOCAL, self.OnTakeLocal)
        wx.EVT_MENU(self, MENU_TAKE_REMOTE, self.OnTakeRemote)

        # Stage/unstage/discard buttons
        self.actionButtons = wx.BoxSizer(wx.VERTICAL)
        self.listRow.Add(self.actionButtons, 0, wx.BOTTOM, 5)

        self.stageButton = wx.Button(self.topPanel, -1, LABEL_STAGE)
        self.unstageButton = wx.Button(self.topPanel, -1, LABEL_UNSTAGE)
        self.discardButton = wx.Button(self.topPanel, -1, LABEL_DISCARD)

        self.Bind(wx.EVT_BUTTON, self.OnStage, self.stageButton)
        self.Bind(wx.EVT_BUTTON, self.OnUnstage, self.unstageButton)
        self.Bind(wx.EVT_BUTTON, self.OnDiscard, self.discardButton)

        self.actionButtons.Add(self.stageButton, 0, wx.EXPAND | wx.TOP, 20)
        self.actionButtons.Add(self.unstageButton, 0, wx.EXPAND | wx.TOP, 5)
        self.actionButtons.Add(self.discardButton, 0, wx.EXPAND | wx.TOP, 20)

        # Staged changes
        self.stagedBox = wx.StaticBox(self.topPanel, -1, "Staged changes")
        self.stagedBoxSizer = wx.StaticBoxSizer(self.stagedBox, wx.VERTICAL)
        self.listRow.Add(self.stagedBoxSizer, 1, wx.EXPAND | wx.LEFT, 10)

        self.stagedList = FileList(self.topPanel, -1)
        self.stagedBoxSizer.Add(self.stagedList, 1, wx.EXPAND|wx.ALL, 0)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnStagedListSelect, self.stagedList)

        # Bottom panel
        self.bottomPanel = wx.Panel(self.splitter, -1)
        self.bottomSizer = wx.BoxSizer(wx.VERTICAL)
        self.bottomPanel.SetSizer(self.bottomSizer)

        # Diff viewer
        self.diffViewer = DiffViewer(self.bottomPanel, -1)
        self.bottomSizer.Add(self.diffViewer, 1, wx.EXPAND)

        # Commit / discard buttons
        self.bottomButtons = wx.BoxSizer(wx.HORIZONTAL)
        self.bottomSizer.Add(self.bottomButtons, 0, wx.TOP | wx.BOTTOM, 5)

        self.commitButton = wx.Button(self.bottomPanel, -1, "Commit staged changes")
        self.resetButton = wx.Button(self.bottomPanel, -1, "Discard all changes")

        self.Bind(wx.EVT_BUTTON, self.OnCommit, self.commitButton)
        self.Bind(wx.EVT_BUTTON, self.OnReset, self.resetButton)

        self.bottomButtons.Add(self.commitButton, 0, wx.LEFT, 5)
        self.bottomButtons.Add(self.resetButton, 0, wx.LEFT, 5)

        # Split window
        self.splitter.SetMinimumPaneSize(120)
        self.splitter.SplitHorizontally(self.topPanel, self.bottomPanel, 200)

    def OnStage(self, e):
        for row in self.unstagedList.GetSelections():
            filename, change = self.unstagedChanges[row]
            if change == FILE_DELETED:
                self.repo.run_cmd(['rm', '--cached', filename])
            else:
                self.repo.run_cmd(['add', filename])

        self.Refresh()

    def OnUnstage(self, e):
        for row in self.stagedList.GetSelections():
            filename = self.stagedChanges[row][0]
            self.repo.run_cmd(['reset', 'HEAD', filename])

        self.Refresh()

    def OnDiscard(self, e):
        # Get selection
        rows = self.unstagedList.GetSelections()
        if len(rows) == 0:
            return

        # Confirm dialog
        msg = wx.MessageDialog(
            self.mainWindow,
            "The selected changes will be permanently lost. Do you really want to continue?",
            "Warning",
            wx.ICON_EXCLAMATION | wx.YES_NO | wx.YES_DEFAULT
        )
        if msg.ShowModal() == wx.ID_YES:
            for row in rows:
                filename = self.unstagedChanges[row][0]
                if filename in self.untrackedFiles:
                    try: os.unlink(os.path.join(self.repo.dir, filename))
                    except OSError: pass
                else:
                    self.repo.run_cmd(['checkout', filename])

        self.Refresh()

    def OnUnstagedListSelect(self, e):
        # Clear selection in stagedList
        for row in self.stagedList.GetSelections():
            self.stagedList.SetItemState(row, 0, wx.LIST_STATE_SELECTED)

        # Show diffs
        selection = self.unstagedList.GetSelections()

        diff_text = ''
        for row in selection:
            filename = self.unstagedChanges[row][0]
            if filename in self.untrackedFiles:
                filename = os.path.join(self.repo.dir, filename)
                diff_text += diff_for_untracked_file(filename)
            else:
                diff_text += self.repo.run_cmd(['diff', self.unstagedChanges[row][0]])

        self.diffViewer.SetDiffText(diff_text)

    def OnStagedListSelect(self, e):
        # Clear selection in unstagedList
        for row in self.unstagedList.GetSelections():
            self.unstagedList.SetItemState(row, 0, wx.LIST_STATE_SELECTED)

        # Show diffs
        selection = self.stagedList.GetSelections()

        diff_text = ''
        for row in selection:
            diff_text += self.repo.run_cmd(['diff', '--cached', self.stagedChanges[row][0]])

        self.diffViewer.SetDiffText(diff_text)

    def OnUnstagedRightClick(self, e):
        id = self.selectedUnstagedItem = e.GetIndex()
        filename, modification = self.unstagedChanges[id]
        submodule_names = [ r.name for r in self.repo.submodules ]

        if modification == FILE_UNMERGED and filename not in submodule_names:
            self.PopupMenu(self.unstagedMenu)

    def OnCommit(self, e):
        if len(self.stagedChanges) == 0:
            return
        if len([c for f,c in self.unstagedChanges if c == FILE_UNMERGED]):
            wx.MessageBox(
                "You should fix conflicts before commiting!",
                "Error",
                style=wx.ICON_EXCLAMATION | wx.OK
            )
            return

        # Show commit wizard
        commit_wizard = CommitWizard(self.mainWindow, -1, self.repo)
        commit_wizard.RunWizard()
        self.mainWindow.SetRepo(self.repo)

    def OnReset(self, e):
        msg = wx.MessageDialog(
            self.mainWindow,
            "This operation will discard ALL (both staged and unstaged) changes. Do you really want to continue?",
            "Warning",
            wx.ICON_EXCLAMATION | wx.YES_NO | wx.YES_DEFAULT
        )
        if msg.ShowModal() == wx.ID_YES:
            self.repo.run_cmd(['reset', '--hard'])
            self.repo.run_cmd(['clean', '-f'])

            self.Refresh()

    def OnMergeFile(self, e):
        self.repo.merge_file(self.unstagedChanges[self.selectedUnstagedItem][0])<commit_msg>bugfix: enable commit during merge mode if there are no staged changes<commit_after># -*- encoding: utf-8

import wx
import wx.html
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin, ListCtrlSelectionManagerMix
import os, os.path
import sys

import Wizard
from DiffViewer import DiffViewer
from git import *
from util import *

MOD_DESCS = {
    FILE_ADDED       : 'added',
    FILE_MODIFIED    : 'modified',
    FILE_DELETED     : 'deleted',
    FILE_COPIED      : 'copied',
    FILE_RENAMED     : 'renamed',
    FILE_UNMERGED    : 'unmerged',
    FILE_TYPECHANGED : 'type changed',
    FILE_UNTRACKED   : 'untracked',
    FILE_BROKEN      : 'BROKEN',
    FILE_UNKNOWN     : 'UNKNOWN'
}

if sys.platform in ['win32', 'cygwin']:
    LABEL_STAGE = u"Stage >"
    LABEL_UNSTAGE = u"< Unstage"
    LABEL_DISCARD = u"× Discard"
else:
    LABEL_STAGE = u"Stage ⇒"
    LABEL_UNSTAGE = u"⇐ Unstage"
    LABEL_DISCARD = u"× Discard"

MENU_MERGE_FILE     = 20000
MENU_TAKE_LOCAL     = 20001
MENU_TAKE_REMOTE    = 20002

class FileList(wx.ListCtrl, ListCtrlAutoWidthMixin, ListCtrlSelectionManagerMix):
    def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.LC_REPORT | wx.LC_NO_HEADER):
        wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
        ListCtrlAutoWidthMixin.__init__(self)
        ListCtrlSelectionManagerMix.__init__(self)

        self.InsertColumn(0, "File")

    def GetSelections(self):
        return [ i for i in xrange(self.GetItemCount()) if self.GetItemState(i, wx.LIST_STATE_SELECTED) == wx.LIST_STATE_SELECTED ]

class IndexTab(wx.Panel):
    def __init__(self, mainWindow, parent, id):
        wx.Panel.__init__(self, parent, id)
        self.mainWindow = mainWindow
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self.sizer)
        
        # Splitter
        self.splitter = wx.SplitterWindow(self, -1, style=wx.SP_LIVE_UPDATE)
        self.sizer.Add(self.splitter, True, wx.EXPAND, wx.ALL)

        # Top panel
        self.topPanel = wx.Panel(self.splitter, -1)
        self.topSizer = wx.BoxSizer(wx.VERTICAL)
        self.topPanel.SetSizer(self.topSizer)

        # --- File lists ---
        self.listRow = wx.BoxSizer(wx.HORIZONTAL)
        self.topSizer.Add(self.listRow, 1, wx.EXPAND)

        # Unstaged changes
        self.unstagedBox = wx.StaticBox(self.topPanel, -1, "Unstaged changes")
        self.unstagedBoxSizer = wx.StaticBoxSizer(self.unstagedBox, wx.VERTICAL)
        self.listRow.Add(self.unstagedBoxSizer, 1, wx.EXPAND | wx.RIGHT, 10)

        self.unstagedList = FileList(self.topPanel, -1)
        self.unstagedBoxSizer.Add(self.unstagedList, 1, wx.EXPAND|wx.ALL, 0)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnUnstagedListSelect, self.unstagedList)
        self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnUnstagedRightClick, self.unstagedList)

        self.unstagedMenu = wx.Menu()
        self.unstagedMenu.Append(MENU_MERGE_FILE, "Merge file")
        self.unstagedMenu.Append(MENU_TAKE_LOCAL, "Take local version")
        self.unstagedMenu.Append(MENU_TAKE_REMOTE, "Take remote version")
        wx.EVT_MENU(self, MENU_MERGE_FILE, self.OnMergeFile)
        wx.EVT_MENU(self, MENU_TAKE_LOCAL, self.OnTakeLocal)
        wx.EVT_MENU(self, MENU_TAKE_REMOTE, self.OnTakeRemote)

        # Stage/unstage/discard buttons
        self.actionButtons = wx.BoxSizer(wx.VERTICAL)
        self.listRow.Add(self.actionButtons, 0, wx.BOTTOM, 5)

        self.stageButton = wx.Button(self.topPanel, -1, LABEL_STAGE)
        self.unstageButton = wx.Button(self.topPanel, -1, LABEL_UNSTAGE)
        self.discardButton = wx.Button(self.topPanel, -1, LABEL_DISCARD)

        self.Bind(wx.EVT_BUTTON, self.OnStage, self.stageButton)
        self.Bind(wx.EVT_BUTTON, self.OnUnstage, self.unstageButton)
        self.Bind(wx.EVT_BUTTON, self.OnDiscard, self.discardButton)

        self.actionButtons.Add(self.stageButton, 0, wx.EXPAND | wx.TOP, 20)
        self.actionButtons.Add(self.unstageButton, 0, wx.EXPAND | wx.TOP, 5)
        self.actionButtons.Add(self.discardButton, 0, wx.EXPAND | wx.TOP, 20)

        # Staged changes
        self.stagedBox = wx.StaticBox(self.topPanel, -1, "Staged changes")
        self.stagedBoxSizer = wx.StaticBoxSizer(self.stagedBox, wx.VERTICAL)
        self.listRow.Add(self.stagedBoxSizer, 1, wx.EXPAND | wx.LEFT, 10)

        self.stagedList = FileList(self.topPanel, -1)
        self.stagedBoxSizer.Add(self.stagedList, 1, wx.EXPAND|wx.ALL, 0)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnStagedListSelect, self.stagedList)

        # Bottom panel
        self.bottomPanel = wx.Panel(self.splitter, -1)
        self.bottomSizer = wx.BoxSizer(wx.VERTICAL)
        self.bottomPanel.SetSizer(self.bottomSizer)

        # Diff viewer
        self.diffViewer = DiffViewer(self.bottomPanel, -1)
        self.bottomSizer.Add(self.diffViewer, 1, wx.EXPAND)

        # Commit / discard buttons
        self.bottomButtons = wx.BoxSizer(wx.HORIZONTAL)
        self.bottomSizer.Add(self.bottomButtons, 0, wx.TOP | wx.BOTTOM, 5)

        self.commitButton = wx.Button(self.bottomPanel, -1, "Commit staged changes")
        self.resetButton = wx.Button(self.bottomPanel, -1, "Discard all changes")

        self.Bind(wx.EVT_BUTTON, self.OnCommit, self.commitButton)
        self.Bind(wx.EVT_BUTTON, self.OnReset, self.resetButton)

        self.bottomButtons.Add(self.commitButton, 0, wx.LEFT, 5)
        self.bottomButtons.Add(self.resetButton, 0, wx.LEFT, 5)

        # Split window
        self.splitter.SetMinimumPaneSize(120)
        self.splitter.SplitHorizontally(self.topPanel, self.bottomPanel, 200)

    def OnStage(self, e):
        for row in self.unstagedList.GetSelections():
            filename, change = self.unstagedChanges[row]
            if change == FILE_DELETED:
                self.repo.run_cmd(['rm', '--cached', filename])
            else:
                self.repo.run_cmd(['add', filename])

        self.Refresh()

    def OnUnstage(self, e):
        for row in self.stagedList.GetSelections():
            filename = self.stagedChanges[row][0]
            self.repo.run_cmd(['reset', 'HEAD', filename])

        self.Refresh()

    def OnDiscard(self, e):
        # Get selection
        rows = self.unstagedList.GetSelections()
        if len(rows) == 0:
            return

        # Confirm dialog
        msg = wx.MessageDialog(
            self.mainWindow,
            "The selected changes will be permanently lost. Do you really want to continue?",
            "Warning",
            wx.ICON_EXCLAMATION | wx.YES_NO | wx.YES_DEFAULT
        )
        if msg.ShowModal() == wx.ID_YES:
            for row in rows:
                filename = self.unstagedChanges[row][0]
                if filename in self.untrackedFiles:
                    try: os.unlink(os.path.join(self.repo.dir, filename))
                    except OSError: pass
                else:
                    self.repo.run_cmd(['checkout', filename])

        self.Refresh()

    def OnUnstagedListSelect(self, e):
        # Clear selection in stagedList
        for row in self.stagedList.GetSelections():
            self.stagedList.SetItemState(row, 0, wx.LIST_STATE_SELECTED)

        # Show diffs
        selection = self.unstagedList.GetSelections()

        diff_text = ''
        for row in selection:
            filename = self.unstagedChanges[row][0]
            if filename in self.untrackedFiles:
                filename = os.path.join(self.repo.dir, filename)
                diff_text += diff_for_untracked_file(filename)
            else:
                diff_text += self.repo.run_cmd(['diff', self.unstagedChanges[row][0]])

        self.diffViewer.SetDiffText(diff_text)

    def OnStagedListSelect(self, e):
        # Clear selection in unstagedList
        for row in self.unstagedList.GetSelections():
            self.unstagedList.SetItemState(row, 0, wx.LIST_STATE_SELECTED)

        # Show diffs
        selection = self.stagedList.GetSelections()

        diff_text = ''
        for row in selection:
            diff_text += self.repo.run_cmd(['diff', '--cached', self.stagedChanges[row][0]])

        self.diffViewer.SetDiffText(diff_text)

    def OnUnstagedRightClick(self, e):
        id = self.selectedUnstagedItem = e.GetIndex()
        filename, modification = self.unstagedChanges[id]
        submodule_names = [ r.name for r in self.repo.submodules ]

        if modification == FILE_UNMERGED and filename not in submodule_names:
            self.PopupMenu(self.unstagedMenu)

    def OnCommit(self, e):
        if len(self.stagedChanges) == 0 and not os.path.exists(os.path.join(self.repo.dir, '.git', 'MERGE_HEAD')):
            return

        if len([c for f,c in self.unstagedChanges if c == FILE_UNMERGED]):
            wx.MessageBox(
                "You should fix conflicts before commiting!",
                "Error",
                style=wx.ICON_EXCLAMATION | wx.OK
            )
            return

        # Show commit wizard
        commit_wizard = CommitWizard(self.mainWindow, -1, self.repo)
        commit_wizard.RunWizard()
        self.mainWindow.SetRepo(self.repo)

    def OnReset(self, e):
        msg = wx.MessageDialog(
            self.mainWindow,
            "This operation will discard ALL (both staged and unstaged) changes. Do you really want to continue?",
            "Warning",
            wx.ICON_EXCLAMATION | wx.YES_NO | wx.YES_DEFAULT
        )
        if msg.ShowModal() == wx.ID_YES:
            self.repo.run_cmd(['reset', '--hard'])
            self.repo.run_cmd(['clean', '-f'])

            self.Refresh()

    def OnMergeFile(self, e):
        self.repo.merge_file(self.unstagedChanges[self.selectedUnstagedItem][0])
{'fraction_non_alphanumeric': 0.11053148081194153, 'fraction_numerical': 0.007954134600485512, 'mean_word_length': 2.5158888687125476, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, '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': '4642263', 'n_tokens_mistral': 6612, 'n_tokens_neox': 6374, 'n_words': 1182}