3
\$\begingroup\$

Recently, the following code was written to help automate some processes that are typically executed while working with a Mercurial database. What is shown below is basically a first draft of the program and was quickly written to test out the idea of creating a GUI tool for others to use.

Question: Are there any obvious flaws in how the program works or how the code is formatted and arranged that might be improved for future versions?

import sys
import subprocess
import tkFileDialog
import Tkinter

class TortoiseHelper(Tkinter.Tk):

    def __init__(self):
        Tkinter.Tk.__init__(self)
        self.initialize()
        self.create_controls()
        if len(sys.argv) > 1:
            if TortoiseHelper.validate_repo(sys.argv[1]):
                self.repoPath.set(sys.argv[1])
                self.refresh_repo()


    def change_repo(self):
        newPath = tkFileDialog.askdirectory(parent=self)
        if TortoiseHelper.validate_repo(newPath):
            self.repoPath.set(newPath)
            self.refresh_repo()


    def create_controls(self):
        mainFrame = Tkinter.Frame(self)
        mainFrame.grid(row=0, column=0, padx=10, pady=10, sticky=(Tkinter.N, Tkinter.E, Tkinter.S, Tkinter.W))
        mainFrame.columnconfigure(1, weight=1)
        mainFrame.rowconfigure(13, weight=1)

        repoLabel = Tkinter.Label(mainFrame)
        repoLabel['text'] = 'Repository'
        repoLabel.grid(row=0, column=0, sticky=Tkinter.E)

        lblRepo = Tkinter.Label(mainFrame)
        lblRepo['textvariable'] = self.repoPath
        lblRepo['relief'] = Tkinter.SUNKEN
        lblRepo['anchor'] = Tkinter.W
        lblRepo.grid(row=0, column=1, ipadx=4, pady=2, sticky=(Tkinter.E, Tkinter.W))

        btnRefresh = Tkinter.Button(mainFrame)
        btnRefresh['text'] = 'Refresh'
        btnRefresh['command'] = self.refresh_repo
        btnRefresh.grid(row=0, column=2, padx=2)

        btnChange = Tkinter.Button(mainFrame)
        btnChange['text'] = 'Change'
        btnChange['command'] = self.change_repo
        btnChange.grid(row=0, column=3, padx=2)

        statusLabel = Tkinter.Label(mainFrame)
        statusLabel['text'] = 'Status'
        statusLabel.grid(row=1, column=0, sticky=(Tkinter.N, Tkinter.E))

        lblStatus = Tkinter.Label(mainFrame)
        lblStatus['textvariable'] = self.repoStatus
        lblStatus['relief'] = Tkinter.SUNKEN
        lblStatus['anchor'] = Tkinter.W
        lblStatus['justify'] = Tkinter.LEFT
        lblStatus.grid(row=1, column=1, ipadx=4, pady=2, sticky=(Tkinter.E, Tkinter.W))

        branchLabel = Tkinter.Label(mainFrame)
        branchLabel['text'] = 'Branch'
        branchLabel.grid(row=2, column=0, sticky=Tkinter.E)

        lblBranch = Tkinter.Label(mainFrame)
        lblBranch['textvariable'] = self.repoBranch
        lblBranch['relief'] = Tkinter.SUNKEN
        lblBranch['anchor'] = Tkinter.W
        lblBranch.grid(row=2, column=1, ipadx=4, pady=2, sticky=(Tkinter.E, Tkinter.W))

        chkCommit = Tkinter.Checkbutton(mainFrame)
        chkCommit['text'] = 'Commit'
        chkCommit['variable'] = self.optionCommit
        chkCommit['command'] = self.on_commit_click
        chkCommit.grid(row=3, column=0, columnspan=4, sticky=Tkinter.W)

        branchNameLabel = Tkinter.Label(mainFrame)
        branchNameLabel['text'] = 'New branch name'
        branchNameLabel.grid(row=4, column=0, sticky=Tkinter.E)

        self.entBranchName = Tkinter.Entry(mainFrame)
        self.entBranchName['textvariable'] = self.newBranchName
        self.entBranchName.grid(row=4, column=1, columnspan=3, sticky=(Tkinter.E, Tkinter.W))

        commitMessageLabel = Tkinter.Label(mainFrame)
        commitMessageLabel['text'] = 'Commit message'
        commitMessageLabel.grid(row=5, column=0, sticky=Tkinter.E)

        self.txtCommitMessage = Tkinter.Text(mainFrame)
        self.txtCommitMessage['height'] = 4
        self.txtCommitMessage.grid(row=5, column=1, columnspan=3, pady=2, sticky=(Tkinter.E, Tkinter.W))

        self.chkClose = Tkinter.Checkbutton(mainFrame)
        self.chkClose['text'] = 'Close branch'
        self.chkClose['variable'] = self.optionClose
        self.chkClose.grid(row=6, column=0, columnspan=4, sticky=Tkinter.W)

        self.chkMergeTest = Tkinter.Checkbutton(mainFrame)
        self.chkMergeTest['text'] = 'Merge with Test'
        self.chkMergeTest['variable'] = self.optionMergeTest
        self.chkMergeTest['command'] = self.on_merge_test_click
        self.chkMergeTest.grid(row=7, column=0, columnspan=4, sticky=Tkinter.W)

        self.chkMergeTestSilent = Tkinter.Checkbutton(mainFrame)
        self.chkMergeTestSilent['text'] = 'Attempt silent merge'
        self.chkMergeTestSilent['variable'] = self.optionSilentMergeTest
        self.chkMergeTestSilent.grid(row=8, column=0, columnspan=4, padx=20, sticky=Tkinter.W)

        self.chkMergeProduction = Tkinter.Checkbutton(mainFrame)
        self.chkMergeProduction['text'] = 'Merge with Production'
        self.chkMergeProduction['variable'] = self.optionMergeProduction
        self.chkMergeProduction['command'] = self.on_merge_production_click
        self.chkMergeProduction.grid(row=9, column=0, columnspan=4, sticky=Tkinter.W)

        self.chkMergeProductionSilent = Tkinter.Checkbutton(mainFrame)
        self.chkMergeProductionSilent['text'] = 'Attempt silent merge'
        self.chkMergeProductionSilent['variable'] = self.optionSilentMergeProduction
        self.chkMergeProductionSilent.grid(row=10, column=0, columnspan=4, padx=20, sticky=Tkinter.W)

        chkPush = Tkinter.Checkbutton(mainFrame)
        chkPush['text'] = 'Push'
        chkPush['variable'] = self.optionPush
        chkPush.grid(row=11, column=0, columnspan=4, sticky=Tkinter.W)

        btnRun = Tkinter.Button(mainFrame)
        btnRun['text'] = 'Run'
        btnRun['command'] = self.run
        btnRun.grid(row=12, column=0, sticky=Tkinter.W)

        btnCancel = Tkinter.Button(mainFrame)
        btnCancel['text'] = 'Cancel'
        btnCancel['command'] = self.quit
        btnCancel.grid(row=12, column=2, columnspan=3, sticky=Tkinter.E)

        self.txtOutput = Tkinter.Text(mainFrame)
        self.txtOutput['state'] = Tkinter.DISABLED
        self.txtOutput.grid(row=13, column=0, pady=4, columnspan=4, sticky=(Tkinter.N, Tkinter.E, Tkinter.S, Tkinter.W))
        self.txtOutput.tag_config('RED', foreground='red')
        self.txtOutput.tag_config('GRAY', foreground='gray')
        self.txtOutput.tag_config('BG_GRAY', background='gray')


    def initialize(self):
        # Initialize variables
        self.repoPath = Tkinter.StringVar()
        self.repoStatus = Tkinter.StringVar()
        self.repoBranch = Tkinter.StringVar()
        self.optionCommit = Tkinter.IntVar()
        self.newBranchName = Tkinter.StringVar()
        self.optionClose = Tkinter.IntVar()
        self.optionMergeTest = Tkinter.IntVar()
        self.optionSilentMergeTest = Tkinter.IntVar()
        self.optionMergeProduction = Tkinter.IntVar()
        self.optionSilentMergeProduction = Tkinter.IntVar()
        self.optionPush = Tkinter.IntVar()

        # Configure the window
        self.title('Tortoise Helper')
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        self.minsize(width=400, height=500)


    @staticmethod
    def is_special_branch(branchName):
        return branchName in ('Test', 'Production', 'Site Redesign')


    def on_commit_click(self):
        self.set_commit_fields_enabled()
        self.set_close_branch_option_enabled()
        self.set_merge_test_option_enabled()
        self.set_merge_production_option_enabled()


    def on_merge_production_click(self):
        self.set_silent_merge_production_option_enabled()


    def on_merge_test_click(self):
        self.set_silent_merge_test_option_enabled()


    def refresh_repo(self):
        path = self.repoPath.get()
        command = 'hg status -R ' + path
        uncommittedChanges = TortoiseHelper.run_command(command)['full_output'] != ''
        repositoryStatus = ''
        if uncommittedChanges == 0:
            repositoryStatus = 'No uncommitted changes'
        elif uncommittedChanges == 1:
            repositoryStatus = '1 uncommitted change'
        else:
            repositoryStatus = str(uncommittedChanges) + ' uncommitted changes'
        command = 'hg incoming -q -R ' + path
        incomingChanges = len(TortoiseHelper.run_command(command)['output'])
        if incomingChanges == 0:
            repositoryStatus += '\nNo incoming changes'
        elif incomingChanges == 1:
            repositoryStatus += '\n1 incoming change'
        else:
            repositoryStatus += '\n' + str(incomingChanges) + ' incoming changes'
        command = 'hg outgoing -q -R ' + path
        outgoingChanges = len(TortoiseHelper.run_command(command)['output'])
        if outgoingChanges == 0:
            repositoryStatus += '\nNo outgoing changes'
        elif outgoingChanges == 1:
            repositoryStatus += '\n1 outgoing change'
        else:
            repositoryStatus += '\n' + str(outgoingChanges) + ' outgoing changes'
        self.repoStatus.set(repositoryStatus)
        command = 'hg branch -R ' + path
        branchName = TortoiseHelper.run_command(command)['full_output']
        self.repoBranch.set(branchName)
        self.optionCommit.set(0)
        self.set_commit_fields_enabled()
        self.set_close_branch_option_enabled()
        self.set_merge_production_option_enabled()
        self.set_merge_test_option_enabled()
        self.optionPush.set(0)
        self.txtOutput.delete(1.0, Tkinter.END)


    def run(self):
        # Determine what needs to be done
        command = ''
        currentRevision = ''
        useMergeDialog = False
        path = self.repoPath.get()
        command = 'hg branch -R ' + path
        currentBranchName = TortoiseHelper.run_command(command)['full_output']
        commit = self.optionCommit.get() == 1
        newBranchName = self.newBranchName.get()
        changeBranch = newBranchName != currentBranchName
        commitMessage = self.txtCommitMessage.get(1.0, Tkinter.END).strip()
        closeBranch = self.optionClose.get() == 1
        mergeWithTest = self.optionMergeTest.get() == 1
        silentMergeTest = self.optionSilentMergeTest.get() == 1
        mergeWithProduction = self.optionMergeProduction.get() == 1
        silentMergeProduction = self.optionSilentMergeProduction.get() == 1
        push = self.optionPush.get() == 1

        # Validate
        if commit:
            if newBranchName == '':
                self.write_output('Branch name is required.', 'RED')
                return
            if TortoiseHelper.is_special_branch(newBranchName):
                self.write_output('Branch name is not valid.', 'RED')
                return
            if commitMessage == '':
                self.write_output('Commit message is required', 'RED')
                return
        if closeBranch:
            if not commit:
                if TortoiseHelper.is_special_branch(currentBranchName):
                    self.write_output('Cannot close branch ' + currentBranchName + '.', 'RED')
                    return

        # Execute
        if commit:
            if changeBranch:
                self.write_output('Changing branch name', 'BG_GRAY')
                command = 'hg branch -R ' + path + ' "' + newBranchName + '"'
                self.write_output(command)
                self.write_output(TortoiseHelper.run_command(command)['full_output'], 'GRAY')
                currentBranchName = newBranchName

            self.write_output('Committing', 'BG_GRAY')
            command = 'hg commit -R ' + path + ' -v "--message=' + commitMessage + '"'
            self.write_output(command)
            self.write_output(TortoiseHelper.run_command(command)['full_output'], 'GRAY')

        if closeBranch:
            self.write_output('Closing', 'BG_GRAY')
            command = 'hg commit -R ' + path + ' -v --close-branch "--message=Close ' + currentBranchName + ' branch"'
            self.write_output(command)
            self.write_output(TortoiseHelper.run_command(command)['full_output'], 'GRAY')

        if mergeWithTest or mergeWithProduction:
            command = 'hg identify -i -R ' + path
            self.write_output(command)
            currentRevision = TortoiseHelper.run_command(command)['full_output']

        if mergeWithTest:
            self.write_output('Pulling', 'BG_GRAY')
            command = 'hg pull -R ' + path
            self.write_output(command)
            self.write_output(TortoiseHelper.run_command(command)['full_output'], 'GRAY')
            self.write_output('Updating to Test', 'BG_GRAY')
            command = 'hg update -R ' + path + ' Test'
            self.write_output(command)
            self.write_output(TortoiseHelper.run_command(command)['full_output'], 'GRAY')
            if silentMergeTest:
                useMergeDialog = False
                self.write_output('Attempting silent merge', 'BG_GRAY')
                command = 'hg merge -R ' + path + ' -t internal:merge -r ' + currentRevision
                self.write_output(command)
                command_result = TortoiseHelper.run_command(command)
                self.write_output(command_result['full_output'], 'GRAY')
                if command_result['return_code'] == 1:
                    self.write_output('Silent merge failed; returning to Test branch', 'BG_GRAY')
                    command = 'hg update -C -R ' + path
                    self.write_output(command)
                    self.write_output(TortoiseHelper.run_command(command)['full_output'], 'GRAY')
                    useMergeDialog = True
            else:
                useMergeDialog = True
            if useMergeDialog:
                self.write_output('Launching merge dialog', 'BG_GRAY')
                command = 'thg merge -R ' + path + ' -r ' + currentRevision
                self.write_output(command)
                self.write_output(TortoiseHelper.run_command(command)['full_output'], 'GRAY')

        if mergeWithProduction:
            self.write_output('Pulling', 'BG_GRAY')
            command = 'hg pull -R ' + path
            self.write_output(command)
            self.write_output(TortoiseHelper.run_command(command)['full_output'], 'GRAY')
            self.write_output('Updating to Production', 'BG_GRAY')
            command = 'hg update -R ' + path + ' Production'
            self.write_output(command)
            self.write_output(TortoiseHelper.run_command(command)['full_output'], 'GRAY')
            if silentMergeProduction:
                useMergeDialog = False
                self.write_output('Attempting silent merge', 'BG_GRAY')
                command = 'hg merge -R ' + path + ' -t internal:merge -r ' + currentRevision
                self.write_output(command)
                command_result = TortoiseHelper.run_command(command)
                self.write_output(command_result['full_output'], 'GRAY')
                if command_result['return_code'] == 1:
                    self.write_output('Silent merge failed; returning to Production branch', 'BG_GRAY')
                    command = 'hg update -C -R ' + path
                    self.write_output(command)
                    self.write_output(TortoiseHelper.run_command(command)['full_output'], 'GRAY')
                    useMergeDialog = True
            else:
                useMergeDialog = True
            if useMergeDialog:
                self.write_output('Launching merge dialog', 'BG_GRAY')
                command = 'thg merge -R ' + path + ' -r ' + currentRevision
                self.write_output(command)
                self.write_output(TortoiseHelper.run_command(command)['full_output'], 'GRAY')

        if push:
            self.write_output('Pushing', 'BG_GRAY')
            command = 'hg push -R ' + path + ' --new-branch'
            self.write_output(command)
            self.write_output(TortoiseHelper.run_command(command)['full_output'], 'GRAY')


    @staticmethod
    def run_command(command):
        output = []
        process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        for line in process.stdout:
            output.append(line.strip())
        return { "return_code": process.wait(), "output": output, "full_output": '\n'.join(output) }


    def set_close_branch_option_enabled(self):
        if TortoiseHelper.is_special_branch(self.repoBranch.get()) and self.optionCommit.get() == 0:
            self.chkClose['state'] = Tkinter.DISABLED
            self.optionClose.set(0)
        else:
            self.chkClose['state'] = Tkinter.NORMAL


    def set_commit_fields_enabled(self):
        if self.optionCommit.get() == 1:
            self.entBranchName['state'] = Tkinter.NORMAL
            self.txtCommitMessage['state'] = Tkinter.NORMAL
        else:
            self.entBranchName['state'] = Tkinter.DISABLED
            self.newBranchName.set(self.repoBranch.get())
            self.txtCommitMessage['state'] = Tkinter.DISABLED
            self.txtCommitMessage.delete(1.0, Tkinter.END)


    def set_merge_production_option_enabled(self):
        if TortoiseHelper.is_special_branch(self.repoBranch.get()) and self.optionCommit.get() == 0:
            self.chkMergeProduction['state'] = Tkinter.DISABLED
            self.optionMergeProduction.set(0)
        else:
            self.chkMergeProduction['state'] = Tkinter.NORMAL
        self.set_silent_merge_production_option_enabled()


    def set_merge_test_option_enabled(self):
        if TortoiseHelper.is_special_branch(self.repoBranch.get()) and self.optionCommit.get() == 0:
            self.chkMergeTest['state'] = Tkinter.DISABLED
            self.optionMergeTest.set(0)
        else:
            self.chkMergeTest['state'] = Tkinter.NORMAL
        self.set_silent_merge_test_option_enabled()


    def set_silent_merge_production_option_enabled(self):
        if self.optionMergeProduction.get() == 1:
            self.chkMergeProductionSilent['state'] = Tkinter.NORMAL
        else:
            self.optionSilentMergeProduction.set(0)
            self.chkMergeProductionSilent['state'] = Tkinter.DISABLED


    def set_silent_merge_test_option_enabled(self):
        if self.optionMergeTest.get() == 1:
            self.chkMergeTestSilent['state'] = Tkinter.NORMAL
        else:
            self.optionSilentMergeTest.set(0)
            self.chkMergeTestSilent['state'] = Tkinter.DISABLED


    @staticmethod
    def validate_repo(path):
        return TortoiseHelper.run_command('hg id -R ' + path)['return_code'] == 0


    def write_output(self, value, tag=''):
        self.txtOutput['state'] = Tkinter.NORMAL
        if len(self.txtOutput.get(1.0, Tkinter.END)) > 1:
            self.txtOutput.insert(Tkinter.END, '\n')
        self.txtOutput.insert(Tkinter.END, value, tag)
        self.txtOutput['state'] = Tkinter.DISABLED


def main():
    helper = TortoiseHelper()
    helper.mainloop()


if (__name__ == '__main__'):
    main()
\$\endgroup\$
1
  • \$\begingroup\$ While I'm not a pro with Mercurial or Tortise or TKinter, you have a lot of style violations here that prompts me having to link you to the PEP8 style rules, specifically line breaks and variable naming. Also, why are you trying to construct the window outside of the __init__, where you very likely should be constructing everything for the class? \$\endgroup\$ Commented Oct 14, 2016 at 18:48

1 Answer 1

2
\$\begingroup\$

The code could be redesigned to make porting to Python 3.X easier in the future. A lot of refactoring is needed, and the following is only a partial redesign. The biggest problem that the program still has is how it locks up while trying to complete long-running operations. My recommendation is to take the following code and use at the starting point for an even more detailed rewrite.

#! /usr/bin/env python2
# http://codereview.stackexchange.com/questions/144238
from __future__ import division             # PEP 238
from __future__ import absolute_import      # PEP 328
from __future__ import print_function       # PEP 3105
from __future__ import unicode_literals     # PEP 3112

import subprocess
import sys

import Tkinter as tkinter
from Tkconstants import *

import tkFileDialog
tkinter.filedialog = tkFileDialog
del tkFileDialog


class TortoiseTool(tkinter.Frame, object):

    __DEFAULT = object()

    @classmethod
    def main(cls, argv=None):
        tkinter.NoDefaultRoot()
        root = tkinter.Tk()
        # Configure the main window.
        root.title('Tortoise Helper')
        root.wm_minsize(400, 500)
        # Create the application with placement.
        tool = cls(root, argv=argv)
        tool.grid(sticky=NSEW)
        # Configure the resizing capabilities.
        root.grid_columnconfigure(0, weight=1)
        root.grid_rowconfigure(0, weight=1)
        root.mainloop()

    def __init__(self, master=None, cnf=__DEFAULT, **kw):
        argv = kw.pop('argv', None)
        super(TortoiseTool, self).__init__(master, self.__eval(cnf, {}), **kw)
        # Initialize the variables used with the widgets.
        self.repo_path = tkinter.StringVar(self)
        self.repo_status = tkinter.StringVar(self)
        self.repo_branch = tkinter.StringVar(self)
        self.option_commit = tkinter.BooleanVar(self)
        self.new_branch_name = tkinter.StringVar(self)
        self.option_close = tkinter.BooleanVar(self)
        self.option_merge_test = tkinter.BooleanVar(self)
        self.option_silent_merge_test = tkinter.BooleanVar(self)
        self.option_merge_production = tkinter.BooleanVar(self)
        self.option_silent_merge_production = tkinter.BooleanVar(self)
        self.option_push = tkinter.BooleanVar(self)
        # Create all widgets used in the applications.
        self.repo_label = tkinter.Label(self, text='Repository')
        self.lbl_repo = tkinter.Label(
            self, textvariable=self.repo_path, relief=SUNKEN, anchor=W
        )
        self.btn_refresh = tkinter.Button(
            self, text='Refresh', command=self.refresh_repo
        )
        self.btn_change = tkinter.Button(
            self, text='Change', command=self.change_repo
        )
        self.status_label = tkinter.Label(self, text='Status')
        self.lbl_status = tkinter.Label(
            self, textvariable=self.repo_status, relief=SUNKEN, anchor=W,
            justify=LEFT
        )
        self.branch_label = tkinter.Label(self, text='Branch')
        self.lbl_branch = tkinter.Label(
            self, textvariable=self.repo_branch, relief=SUNKEN, anchor=W
        )
        self.chk_commit = tkinter.Checkbutton(
            self, text='Commit', variable=self.option_commit,
            command=self.on_commit_click
        )
        self.ent_branch_name = tkinter.Entry(
            self, textvariable=self.new_branch_name
        )
        self.branch_name_label = tkinter.Label(self, text='New branch name')
        self.commit_message_label = tkinter.Label(self, text='Commit message')
        self.txt_commit_message = tkinter.Text(self, height=4)
        self.chk_close = tkinter.Checkbutton(
            self, text='Close branch', variable=self.option_close
        )
        self.chk_merge_test = tkinter.Checkbutton(
            self, text='Merge with Test', variable=self.option_merge_test,
            command=self.on_merge_test_click
        )
        self.chk_merge_test_silent = tkinter.Checkbutton(
            self, text='Attempt silent merge', state=DISABLED,
            variable=self.option_silent_merge_test
        )
        self.chk_merge_production = tkinter.Checkbutton(
            self, text='Merge with Production',
            variable=self.option_merge_production,
            command=self.on_merge_production_click
        )
        self.chk_merge_production_silent = tkinter.Checkbutton(
            self, text='Attempt silent merge', state=DISABLED,
            variable=self.option_silent_merge_production
        )
        self.chk_push = tkinter.Checkbutton(
            self, text='Push', variable=self.option_push
        )
        self.btn_run = tkinter.Button(self, text='Run', command=self.run)
        self.btn_cancel = tkinter.Button(
            self, text='Cancel', command=self.quit
        )
        self.txt_output = tkinter.Text(self, state=DISABLED)
        # Finish the setup process and configure parameters.
        self.grid_all_widgets()
        self.adjust_resizing_weights()
        self.configure_widget_options()
        self.process_command_line_arguments(argv)

    def grid_all_widgets(self):
        self.repo_label.grid(row=0, column=0, sticky=E)
        self.lbl_repo.grid(row=0, column=1, padx=4, pady=2, sticky=EW)
        self.btn_refresh.grid(row=0, column=2, padx=2)
        self.btn_change.grid(row=0, column=3, padx=2)
        self.status_label.grid(row=1, column=0, sticky=NE)
        self.lbl_status.grid(row=1, column=1, padx=4, pady=2, sticky=EW)
        self.branch_label.grid(row=2, column=0, sticky=E)
        self.lbl_branch.grid(row=2, column=1, padx=4, pady=2, sticky=EW)
        self.chk_commit.grid(row=3, column=0, columnspan=4, sticky=W)
        self.branch_name_label.grid(row=4, column=0, sticky=E)
        self.ent_branch_name.grid(row=4, column=1, columnspan=3, sticky=EW)
        self.commit_message_label.grid(row=5, column=0, sticky=E)
        self.txt_commit_message.grid(
            row=5, column=1, columnspan=3, pady=2, sticky=EW
        )
        self.chk_close.grid(row=6, column=0, columnspan=4, sticky=W)
        self.chk_merge_test.grid(row=7, column=0, columnspan=4, sticky=W)
        self.chk_merge_test_silent.grid(
            row=8, column=0, columnspan=4, padx=20, sticky=W
        )
        self.chk_merge_production.grid(row=9, column=0, columnspan=4, sticky=W)
        self.chk_merge_production_silent.grid(
            row=10, column=0, columnspan=4, padx=20, sticky=W
        )
        self.chk_push.grid(row=11, column=0, columnspan=4, sticky=W)
        self.btn_run.grid(row=12, column=0, sticky=W)
        self.btn_cancel.grid(row=12, column=1, columnspan=3, sticky=E)
        self.txt_output.grid(
            row=13, column=0, pady=4, columnspan=4, sticky=NSEW
        )

    def adjust_resizing_weights(self):
        self.grid_rowconfigure(13, weight=1)
        self.grid_columnconfigure(1, weight=1)

    def configure_widget_options(self):
        self.txt_output.tag_configure('RED', foreground='red')
        self.txt_output.tag_configure('GRAY', foreground='gray')
        self.txt_output.tag_configure('BG_GRAY', background='gray')

    def process_command_line_arguments(self, argv):
        if argv is not None and len(argv) > 1:
            self.validate_and_refresh(argv[1])

    def validate_and_refresh(self, repo):
        if self.validate_repo(repo):
            self.repo_path.set(repo)
            self.refresh_repo()

    @classmethod
    def __eval(cls, obj, default):
        return default if obj is cls.__DEFAULT else obj

    @staticmethod
    def validate_repo(path):
        try:
            subprocess.check_call(('hg', 'id', '-R', path))
        except subprocess.CalledProcessError:
            return False
        else:
            return True

    def refresh_repo(self):
        path = self.repo_path.get()
        repository_status = '\n'.join(
            self.format_count(
                len(get_output(command).splitlines()), message
            ) for command, message in (
                (('hg', 'status', '-R', path), 'uncommitted change'),
                (('hg', 'incoming', '-q', '-R', path), 'incoming change'),
                (('hg', 'outgoing', '-q', '-R', path), 'outgoing change')
            )
        )
        self.repo_status.set(repository_status)
        command = 'hg', 'branch', '-R', path
        self.repo_branch.set(subprocess.check_output(command).strip())
        self.reset_options()
        self.txt_output.delete(1.0, END)

    @staticmethod
    def format_count(count, message):
        return ('No {message}s', '1 {message}', '{count} {message}s')[
            min(count, 2)
        ].format(**locals())

    def reset_options(self, reset_commit=True):
        if reset_commit:
            self.option_commit.set(False)
        self.option_close.set(False)
        self.option_merge_test.set(False)
        self.on_merge_test_click()
        self.option_merge_production.set(False)
        self.on_merge_production_click()
        self.option_push.set(False)

    def change_repo(self):
        self.validate_and_refresh(tkinter.filedialog.askdirectory(parent=self))

    def on_commit_click(self):
        self.reset_options(False)

    def on_merge_test_click(self):
        if self.option_merge_test.get():
            self.chk_merge_test_silent['state'] = NORMAL
        else:
            self.chk_merge_test_silent['state'] = DISABLED
            self.option_silent_merge_test.set(False)

    def on_merge_production_click(self):
        if self.option_merge_production.get():
            self.chk_merge_production_silent['state'] = NORMAL
        else:
            self.chk_merge_production_silent['state'] = DISABLED
            self.option_silent_merge_production.set(False)

    def run(self):
        path = self.repo_path.get()
        current_branch_name = subprocess.check_output(
            ('hg', 'branch', '-R', path)
        ).strip()
        if self.run_is_valid(current_branch_name):
            current_branch_name = self.run_commit(path, current_branch_name)
            self.run_close(path, current_branch_name)
            current_revision = self.get_current_revision(path)
            self.run_merge_test(path, current_revision)
            self.run_merge_production(path, current_revision)
            self.run_push(path)

    def run_is_valid(self, current_branch_name):
        option_commit = self.option_commit.get()
        if option_commit:
            new_branch_name = self.new_branch_name.get()
            if not new_branch_name:
                self.write_output('Branch name is required.', 'RED')
                return False
            if self.is_special_branch(new_branch_name):
                self.write_output('Branch name is not valid.', 'RED')
                return False
            if not self.txt_commit_message.get(1.0, END).strip():
                self.write_output('Commit message is required.', 'RED')
                return False
        if self.option_close.get() and not option_commit and \
                self.is_special_branch(current_branch_name):
            self.write_output(
                'Cannot close branch {}.'.format(current_branch_name), 'RED'
            )
            return False
        return True

    def run_command(self, description, *command):
        self.write_output(description, 'BG_GRAY')
        self.write_output(repr(command))
        self.write_output(subprocess.check_output(command), 'GRAY')

    def write_output(self, value, tag=''):
        self.txt_output['state'] = NORMAL
        if self.txt_output.get(1.0, END):
            self.txt_output.insert(END, '\n')
        self.txt_output.insert(END, value, tag)
        self.txt_output['state'] = DISABLED

    def run_commit(self, path, current_branch_name):
        if self.option_commit.get():
            new_branch_name = self.new_branch_name.get()
            if new_branch_name != current_branch_name:
                self.run_command(
                    'Changing branch name', 'hg', 'branch', '-R', path,
                    new_branch_name
                )
                current_branch_name = new_branch_name
            self.run_command(
                'Committing', 'hg', 'commit', '-R', path, '-v',
                '--message' + self.txt_commit_message.get(1.0, END).strip()
            )
        return current_branch_name

    def run_close(self, path, current_branch_name):
        if self.option_close.get():
            self.run_command(
                'Closing', 'hg', 'commit', '-R', path, '-v', '--close-branch',
                '--message=Close {} branch'.format(current_branch_name)
            )

    def get_current_revision(self, path, current_revision=''):
        if self.option_merge_test.get() or self.option_merge_production.get():
            command = 'hg', 'identify', '-i', '-R', path
            self.write_output(repr(command))
            current_revision = subprocess.check_output(command).strip()
        return current_revision

    def run_merge_test(self, path, current_revision):
        self.run_merge_generic(
            path, current_revision, self.option_merge_test,
            self.option_silent_merge_test, 'Test'
        )

    def run_merge_production(self, path, current_revision):
        self.run_merge_generic(
            path, current_revision, self.option_merge_production,
            self.option_silent_merge_production, 'Production'
        )

    def run_merge_generic(self, path, revision, merge, silent_merge, name):
        if merge.get():
            self.run_command('Pulling', 'hg', 'pull', '-R', path)
            self.run_command(
                'Updating to {}'.format(name), 'hg', 'update', '-R', path, name
            )
            use_merge_dialog = True
            if silent_merge.get():
                try:
                    self.run_command(
                        'Attempting silent merge', 'hg', 'merge', '-R', path,
                        '-t', 'internal:merge', '-r', revision
                    )
                except subprocess.CalledProcessError as error:
                    self.write_output(error.output, 'GRAY')
                    self.run_command(
                        'Silent merge failed; returning to {} branch'.format(
                            name), 'hg', 'update', '-C', '-R', path
                    )
                else:
                    use_merge_dialog = False
            if use_merge_dialog:
                self.run_command(
                    'Launching merge dialog', 'thg', 'merge', '-R', path, '-r',
                    revision
                )

    def run_push(self, path):
        if self.option_push.get():
            self.run_command(
                'Pushing', 'hg', 'push', '-R', path, '--new-branch'
            )

    @staticmethod
    def is_special_branch(branch_name):
        return branch_name in {'Test', 'Production', 'Site Redesign'}

    def set_close_branch_option_enabled(self):
        raise NotImplementedError()

    def set_commit_files_enabled(self):
        raise NotImplementedError()

    def set_merge_production_option_enabled(self):
        raise NotImplementedError()

    def set_merge_test_option_enabled(self):
        raise NotImplementedError()

    def set_silent_merge_production_option_enabled(self):
        raise NotImplementedError()

    def set_silent_merge_test_option_enabled(self):
        raise NotImplementedError()


def get_output(args):
    return subprocess.Popen(args, stdout=subprocess.PIPE).communicate()[0]


if __name__ == '__main__':
    TortoiseTool.main(sys.argv)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.