Browse Source

Merge branch 'release/1.12.0'

liuyuqi-dellpc 2 months ago
parent
commit
8729850a68
10 changed files with 305 additions and 887 deletions
  1. 2 1
      .github/workflows/build.yml
  2. 13 0
      build.bat
  3. 13 0
      build.sh
  4. 0 821
      gui_main.py
  5. 203 55
      main_gui.py
  6. 7 2
      repo_sync/__init__.py
  7. 9 1
      repo_sync/options.py
  8. 15 3
      repo_sync/utils/config_reader.py
  9. 3 2
      requirements-win.txt
  10. 40 2
      setup.py

+ 2 - 1
.github/workflows/build.yml

@@ -35,12 +35,13 @@ jobs:
       - name: Install Dependencies
       - name: Install Dependencies
         run: |
         run: |
           python -m pip install --upgrade pip wheel setuptools
           python -m pip install --upgrade pip wheel setuptools
-          pip install -r requirements.txt
+          pip install -r requirements-win.txt
           python -m pip install pyinstaller
           python -m pip install pyinstaller
 
 
       - name: Build Package
       - name: Build Package
         run: |
         run: |
           python -m PyInstaller -F -c  -i favicon.ico --name repo_sync main.py
           python -m PyInstaller -F -c  -i favicon.ico --name repo_sync main.py
+          python -m PyInstaller -F -c  -i favicon.ico --name repo_sync_gui main_gui.py
 
 
       - name: Update to ali oss
       - name: Update to ali oss
         uses: yizhoumo/setup-ossutil@v1
         uses: yizhoumo/setup-ossutil@v1

+ 13 - 0
build.bat

@@ -0,0 +1,13 @@
+@echo off
+echo Building repo_sync GUI executable...
+
+REM Install required packages
+pip install pyinstaller
+pip install -e .
+
+REM Build the executable
+pyinstaller --onefile --windowed --name "RepoSync" --icon=icon.ico --add-data "repo_sync/config.yml;repo_sync" main_gui.py
+
+echo.
+echo Build completed! The executable is in the dist folder.
+echo. 

+ 13 - 0
build.sh

@@ -0,0 +1,13 @@
+#!/bin/bash
+echo "Building repo_sync GUI executable..."
+
+# Install required packages
+pip install pyinstaller
+pip install -e .
+
+# Build the executable
+pyinstaller --onefile --windowed --name "RepoSync" --add-data "repo_sync/config.yml:repo_sync" main_gui.py
+
+echo ""
+echo "Build completed! The executable is in the dist folder."
+echo "" 

+ 0 - 821
gui_main.py

@@ -1,821 +0,0 @@
-#!/usr/bin/env python
-# -*- encoding: utf-8 -*-
-'''
-@Contact :   liuyuqi.gov@msn.cn
-@Time    :   2023/04/27 02:55:59
-@License :   Copyright © 2017-2022 liuyuqi. All Rights Reserved.
-@Desc    :   repo_sync GUI入口
-'''
-import sys
-import os
-import threading
-import subprocess
-try:
-    from PyQt5.QtWidgets import (
-        QApplication, QTabWidget, QWidget, QVBoxLayout, QHBoxLayout, QLabel, 
-        QRadioButton, QPushButton, QButtonGroup, QGroupBox, QMessageBox, 
-        QLineEdit, QScrollArea, QFileDialog, QFormLayout, QCheckBox, QTextEdit,
-        QComboBox, QDialog, QDialogButtonBox, QGridLayout, QToolButton, QListWidget, QListWidgetItem
-    )
-    from PyQt5.QtCore import Qt, pyqtSignal, QObject
-    HAS_QT = True
-except ImportError:
-    HAS_QT = False
-    print("PyQt5 not installed, GUI mode not available")
-
-# 直接导入repo_sync模块
-sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
-try:
-    from repo_sync.repo_sync import RepoSync
-    from repo_sync.version import __version__
-except ImportError:
-    print("无法导入repo_sync模块,尝试直接导入...")
-    try:
-        from repo_sync import RepoSync
-        from repo_sync.version import __version__
-    except ImportError:
-        print("导入repo_sync模块失败,请确保repo_sync已正确安装")
-        __version__ = "未知"
-        
-        # 创建一个空的RepoSync类作为替代
-        class RepoSync:
-            def __init__(self, params):
-                self.params = params
-            def run(self):
-                print("RepoSync模块未找到,无法执行操作")
-
-try:
-    from dotenv import load_dotenv, set_key, find_dotenv, dotenv_values
-except ImportError:
-    print("python-dotenv未安装,请使用pip install python-dotenv安装")
-    sys.exit(1)
-    
-import json
-import uuid
-
-# 确保.env文件存在
-def ensure_env_file():
-    env_file = find_dotenv()
-    if not env_file:
-        # 创建空的.env文件
-        env_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), '.env')
-        with open(env_file, 'w') as f:
-            f.write("# repo_sync配置文件\n")
-    return env_file
-
-# Explorer路径获取
-try:
-    import win32com.client
-    def get_active_explorer_path():
-        shell = win32com.client.Dispatch("Shell.Application")
-        for window in shell.Windows():
-            if window.Name in ["文件资源管理器", "Windows Explorer"]:
-                return window.Document.Folder.Self.Path
-        return os.getcwd()
-except ImportError:
-    def get_active_explorer_path():
-        return os.getcwd()
-
-# 命令执行信号类
-class CommandSignals(QObject):
-    output = pyqtSignal(str)
-    finished = pyqtSignal(int)
-
-class MainTab(QWidget):
-    def __init__(self, parent=None):
-        super().__init__(parent)
-        layout = QVBoxLayout()
-        
-        # 路径选择
-        path_layout = QHBoxLayout()
-        self.path_label = QLabel("Local Path: ")
-        self.path_edit = QLineEdit()
-        self.path_edit.setText(get_active_explorer_path())
-        self.path_btn = QPushButton("Browse...")
-        self.path_btn.clicked.connect(self.choose_path)
-        path_layout.addWidget(self.path_label)
-        path_layout.addWidget(self.path_edit)
-        path_layout.addWidget(self.path_btn)
-        layout.addLayout(path_layout)
-        
-        # 操作
-        op_group = QGroupBox("Operation:")
-        op_layout = QHBoxLayout()
-        self.op_buttons = QButtonGroup(self)
-        for i, op in enumerate(["create", "push", "pull", "clone", "delete"]):
-            btn = QRadioButton(op.capitalize())
-            if i == 0:
-                btn.setChecked(True)
-            self.op_buttons.addButton(btn, i)
-            op_layout.addWidget(btn)
-        op_group.setLayout(op_layout)
-        layout.addWidget(op_group)
-        
-        # 平台
-        pf_group = QGroupBox("Platform:")
-        pf_layout = QHBoxLayout()
-        self.pf_buttons = QButtonGroup(self)
-        self.platforms = ["github", "gitlab", "gitee", "gitcode", "git.yoq.me", "coding", "aliyun", "cnb"]
-        for i, pf in enumerate(self.platforms):
-            btn = QRadioButton(pf.capitalize())
-            if i == 0:
-                btn.setChecked(True)
-            self.pf_buttons.addButton(btn, i)
-            pf_layout.addWidget(btn)
-        pf_group.setLayout(pf_layout)
-        layout.addWidget(pf_group)
-        
-        # 账户选择
-        account_layout = QHBoxLayout()
-        account_layout.addWidget(QLabel("Account:"))
-        self.account_combo = QComboBox()
-        self.account_combo.setMinimumWidth(200)
-        account_layout.addWidget(self.account_combo)
-        account_layout.addStretch()
-        layout.addLayout(account_layout)
-        
-        # 按钮区域 (run和cancel)
-        btn_layout = QHBoxLayout()
-        self.run_btn = QPushButton("run it")
-        self.run_btn.clicked.connect(self.run_repo_sync)
-        self.cancel_btn = QPushButton("Cancel")
-        self.cancel_btn.clicked.connect(self.cancel_operation)
-        self.cancel_btn.setEnabled(False)
-        btn_layout.addWidget(self.run_btn)
-        btn_layout.addWidget(self.cancel_btn)
-        layout.addLayout(btn_layout)
-        
-        # 命令执行结果
-        result_group = QGroupBox("Execution Result:")
-        result_layout = QVBoxLayout()
-        self.result_text = QTextEdit()
-        self.result_text.setReadOnly(True)
-        result_layout.addWidget(self.result_text)
-        result_group.setLayout(result_layout)
-        layout.addWidget(result_group)
-        
-        self.setLayout(layout)
-        
-        # 命令执行相关
-        self.process = None
-        self.command_signals = CommandSignals()
-        self.command_signals.output.connect(self.update_output)
-        self.command_signals.finished.connect(self.process_finished)
-        
-        # 平台变更时更新账户列表
-        self.pf_buttons.buttonClicked.connect(self.update_account_list)
-        self.update_account_list()
-
-    def choose_path(self):
-        path = QFileDialog.getExistingDirectory(self, "Select Directory")
-        if path:
-            self.path_edit.setText(path)
-
-    def update_account_list(self):
-        self.account_combo.clear()
-        pf_id = self.pf_buttons.checkedId()
-        platform = self.platforms[pf_id]
-        
-        # 读取所有账户
-        accounts = self.get_platform_accounts(platform)
-        
-        # 找出启用的账户
-        enabled_account = "default"
-        env_values = dotenv_values(find_dotenv())
-        for account in accounts:
-            if account != "default" and env_values.get(f"{platform}_{account}_enabled", "").lower() == "true":
-                enabled_account = account
-                break
-        
-        # 添加账户到下拉框,启用的账户放在最前面
-        if enabled_account in accounts:
-            accounts.remove(enabled_account)
-            self.account_combo.addItem(f"{enabled_account} (启用中)")
-            
-        for account in accounts:
-            self.account_combo.addItem(account)
-        
-        # 默认选择启用的账户
-        self.account_combo.setCurrentIndex(0)
-
-    def get_platform_accounts(self, platform):
-        # 读取.env文件中的所有配置
-        env_values = dotenv_values(find_dotenv())
-        accounts = set()
-        
-        # 默认账户
-        accounts.add("default")
-        
-        # 查找带有账户名的配置
-        prefix = f"{platform}_"
-        for key in env_values.keys():
-            if key.startswith(prefix) and "_" in key[len(prefix):]:
-                account_name = key[len(prefix):].split("_")[0]
-                if account_name != "default" and account_name != "":
-                    accounts.add(account_name)
-        
-        return sorted(list(accounts))
-
-    def run_repo_sync(self):
-        repo_path = self.path_edit.text().strip()
-        if not repo_path:
-            QMessageBox.warning(self, "Warning", "Please select a local path.")
-            return
-            
-        op_id = self.op_buttons.checkedId()
-        pf_id = self.pf_buttons.checkedId()
-        op = ["create", "push", "pull", "clone", "delete"][op_id]
-        pf = self.platforms[pf_id]
-        
-        # 获取选择的账户名(去掉可能的"(启用中)"后缀)
-        account_text = self.account_combo.currentText()
-        account = account_text.split(" (")[0] if " (" in account_text else account_text
-        
-        # 清空结果区域
-        self.result_text.clear()
-        
-        # 检查平台配置
-        load_dotenv()
-        token_key = f"{pf}_{account}_token" if account != "default" else f"{pf}_token"
-        if not os.getenv(token_key):
-            QMessageBox.warning(self, "Warning", f"Please configure {pf} token for account '{account}' in Settings tab first.")
-            return
-            
-        # 构建命令
-        cmd = [sys.executable, "-m", "repo_sync"]
-        cmd.append(op)
-        cmd.extend(["-p", pf])
-        cmd.extend(["-repo_path", repo_path])
-        
-        # 如果不是默认账户,需要设置环境变量
-        env = os.environ.copy()
-        if account != "default":
-            # 读取账户配置
-            env_values = dotenv_values(find_dotenv())
-            for key, value in env_values.items():
-                if key.startswith(f"{pf}_{account}_"):
-                    field = key[len(f"{pf}_{account}_"):]
-                    env[f"{pf}_{field}"] = value
-        
-        # 执行命令
-        self.run_btn.setEnabled(False)
-        self.cancel_btn.setEnabled(True)
-        self.result_text.append(f"Running: {' '.join(cmd)}\n")
-        
-        # 在新线程中执行命令
-        self.process_thread = threading.Thread(
-            target=self.run_process,
-            args=(cmd, env)
-        )
-        self.process_thread.daemon = True
-        self.process_thread.start()
-
-    def run_process(self, cmd, env=None):
-        try:
-            self.process = subprocess.Popen(
-                cmd,
-                stdout=subprocess.PIPE,
-                stderr=subprocess.STDOUT,
-                text=True,
-                bufsize=1,
-                universal_newlines=True,
-                env=env
-            )
-            
-            # 读取输出
-            for line in self.process.stdout:
-                self.command_signals.output.emit(line)
-            
-            self.process.wait()
-            self.command_signals.finished.emit(self.process.returncode)
-        except Exception as e:
-            self.command_signals.output.emit(f"Error: {str(e)}")
-            self.command_signals.finished.emit(1)
-
-    def update_output(self, text):
-        self.result_text.append(text)
-        # 自动滚动到底部
-        self.result_text.verticalScrollBar().setValue(
-            self.result_text.verticalScrollBar().maximum()
-        )
-
-    def process_finished(self, return_code):
-        self.process = None
-        self.run_btn.setEnabled(True)
-        self.cancel_btn.setEnabled(False)
-        
-        if return_code == 0:
-            self.result_text.append("\nOperation completed successfully.")
-        else:
-            self.result_text.append(f"\nOperation failed with return code {return_code}.")
-
-    def cancel_operation(self):
-        if self.process:
-            self.process.terminate()
-            self.result_text.append("\nOperation cancelled by user.")
-
-# 添加账户对话框
-class AddAccountDialog(QDialog):
-    def __init__(self, platform, parent=None):
-        super().__init__(parent)
-        self.platform = platform
-        self.setWindowTitle(f"Add {platform.capitalize()} Account")
-        self.resize(400, 200)
-        
-        layout = QVBoxLayout()
-        
-        form_layout = QFormLayout()
-        self.account_name = QLineEdit()
-        form_layout.addRow("Account Name:", self.account_name)
-        
-        # 根据平台添加相应字段
-        self.fields = {}
-        platform_fields = {
-            "github": ["username", "token", "private"],
-            "gitlab": ["host", "username", "token", "private"],
-            "gitee": ["username", "token", "private"],
-            "gitcode": ["username", "token", "private"],
-            "git.yoq.me": ["username", "token", "private"],
-            "coding": ["username", "token", "project", "private"],
-            "aliyun": ["compoanyid", "group_id", "username", "token", "private"],
-            "cnb": ["username", "token", "private"]
-        }
-        
-        for field in platform_fields.get(platform, ["username", "token"]):
-            if field == "private":
-                widget = QCheckBox()
-                widget.setChecked(True)
-            else:
-                widget = QLineEdit()
-                if field in ["token", "password"]:
-                    widget.setEchoMode(QLineEdit.Password)
-            
-            self.fields[field] = widget
-            form_layout.addRow(f"{field.capitalize()}:", widget)
-        
-        layout.addLayout(form_layout)
-        
-        # 按钮
-        buttons = QDialogButtonBox(
-            QDialogButtonBox.Ok | QDialogButtonBox.Cancel
-        )
-        buttons.accepted.connect(self.accept)
-        buttons.rejected.connect(self.reject)
-        layout.addWidget(buttons)
-        
-        self.setLayout(layout)
-    
-    def get_account_data(self):
-        data = {"name": self.account_name.text()}
-        for field, widget in self.fields.items():
-            if isinstance(widget, QCheckBox):
-                data[field] = widget.isChecked()
-            else:
-                data[field] = widget.text()
-        return data
-
-class SettingsTab(QWidget):
-    def __init__(self, parent=None):
-        super().__init__(parent)
-        # 确保.env文件存在
-        ensure_env_file()
-        # 初始化account_lists字典
-        self.account_lists = {}
-        self.init_ui()
-
-    def init_ui(self):
-        layout = QVBoxLayout()
-        
-        # 创建平台选择标签页
-        self.platform_tabs = QTabWidget()
-        
-        # 平台配置
-        self.platform_configs = {
-            "github": ["username", "token", "private"],
-            "gitlab": ["host", "username", "token", "private"],
-            "gitee": ["username", "token", "private"],
-            "gitcode": ["username", "token", "private"],
-            "git.yoq.me": ["username", "token", "private"],
-            "coding": ["username", "token", "project", "private"],
-            "aliyun": ["compoanyid", "group_id", "username", "token", "private"],
-            "cnb": ["username", "token", "private"]
-        }
-        
-        # 为每个平台创建标签页
-        self.platform_pages = {}
-        for platform in self.platform_configs.keys():
-            page = QWidget()
-            page_layout = QVBoxLayout()
-            
-            # 账户管理区域
-            account_group = QGroupBox("账户管理")
-            account_layout = QVBoxLayout()
-            
-            # 账户列表
-            accounts_list_layout = QHBoxLayout()
-            
-            # 左侧:账户列表
-            account_list = QListWidget()
-            account_list.setMinimumWidth(200)
-            account_list.itemClicked.connect(lambda item, p=platform: self.select_account(p, item))
-            account_list.setStyleSheet("""
-                QListWidget::item:selected { background-color: #a6d8ff; }
-                QListWidget::item[enabled="true"] { font-weight: bold; color: #0066cc; }
-            """)
-            accounts_list_layout.addWidget(account_list)
-            self.account_lists[platform] = account_list
-            
-            # 右侧:账户详情
-            account_details = QWidget()
-            account_form = QFormLayout()
-            account_details.setLayout(account_form)
-            accounts_list_layout.addWidget(account_details, 1)
-            
-            account_layout.addLayout(accounts_list_layout)
-            
-            # 账户操作按钮
-            buttons_layout = QHBoxLayout()
-            add_btn = QPushButton("添加账户")
-            add_btn.clicked.connect(lambda checked=False, p=platform: self.add_account(p))
-            delete_btn = QPushButton("删除账户")
-            delete_btn.clicked.connect(lambda checked=False, p=platform: self.delete_account(p))
-            enable_btn = QPushButton("设为启用")
-            enable_btn.clicked.connect(lambda checked=False, p=platform: self.enable_account(p))
-            
-            buttons_layout.addWidget(add_btn)
-            buttons_layout.addWidget(delete_btn)
-            buttons_layout.addWidget(enable_btn)
-            buttons_layout.addStretch()
-            
-            account_layout.addLayout(buttons_layout)
-            account_group.setLayout(account_layout)
-            page_layout.addWidget(account_group)
-            
-            # 保存页面引用
-            self.platform_pages[platform] = {
-                "page": page,
-                "form": account_form,
-                "details": account_details
-            }
-            
-            page.setLayout(page_layout)
-            self.platform_tabs.addTab(page, platform.capitalize())
-        
-        layout.addWidget(self.platform_tabs)
-        
-        # 保存按钮
-        self.save_btn = QPushButton("保存设置")
-        self.save_btn.clicked.connect(self.save_settings)
-        layout.addWidget(self.save_btn, alignment=Qt.AlignCenter)
-        
-        self.setLayout(layout)
-        
-        # 加载设置
-        try:
-            self.load_settings()
-        except Exception as e:
-            print(f"加载设置时出错: {e}")
-            import traceback
-            traceback.print_exc()
-        
-        # 连接标签页变更事件
-        self.platform_tabs.currentChanged.connect(self.tab_changed)
-
-    def tab_changed(self, index):
-        try:
-            # 使用有序字典确保顺序一致性
-            platforms = list(self.platform_configs.keys())
-            if index < 0 or index >= len(platforms):
-                return
-            platform = platforms[index]
-            self.update_account_details(platform)
-        except Exception as e:
-            print(f"Error in tab_changed: {e}")
-
-    def load_settings(self):
-        # 读取.env文件
-        try:
-            env_file = find_dotenv()
-            if not env_file:
-                print("找不到.env文件,创建一个空的")
-                env_file = ensure_env_file()
-                
-            env_values = dotenv_values(env_file)
-            print(f"成功读取.env文件: {env_file}")
-            
-            # 为每个平台加载账户
-            for platform in self.platform_configs.keys():
-                if platform not in self.account_lists:
-                    print(f"警告: 找不到平台 {platform} 的账户列表")
-                    continue
-                    
-                account_list = self.account_lists[platform]
-                account_list.clear()
-                
-                # 查找该平台的所有账户
-                accounts = self.get_platform_accounts(platform, env_values)
-                
-                # 添加到列表
-                for account in accounts:
-                    item = QListWidgetItem(account)
-                    # 标记默认账户
-                    if account == "default":
-                        item.setData(Qt.UserRole, "default")
-                        # 检查是否有其他账户被设为启用
-                        is_enabled = True
-                        for other_account in accounts:
-                            if other_account != "default" and self.is_account_enabled(platform, other_account, env_values):
-                                is_enabled = False
-                                break
-                        item.setData(Qt.UserRole + 1, is_enabled)
-                    else:
-                        item.setData(Qt.UserRole, account)
-                        item.setData(Qt.UserRole + 1, self.is_account_enabled(platform, account, env_values))
-                    
-                    # 设置启用状态的显示
-                    if item.data(Qt.UserRole + 1):
-                        item.setData(Qt.UserRole + 2, "true")
-                        item.setText(f"{account} (启用中)")
-                    
-                    account_list.addItem(item)
-                
-                # 选择第一个账户
-                if account_list.count() > 0:
-                    account_list.setCurrentRow(0)
-                    self.select_account(platform, account_list.item(0))
-        except Exception as e:
-            print(f"加载设置时出错: {e}")
-            import traceback
-            traceback.print_exc()
-
-    def is_account_enabled(self, platform, account, env_values=None):
-        """检查账户是否被设为启用"""
-        if env_values is None:
-            env_values = dotenv_values(find_dotenv())
-        
-        if account == "default":
-            # 默认账户,检查是否有其他账户被设为启用
-            for key in env_values.keys():
-                if key.startswith(f"{platform}_") and "_enabled" in key and env_values[key].lower() == "true":
-                    return False
-            return True
-        else:
-            # 其他账户,检查是否有enabled标记
-            return env_values.get(f"{platform}_{account}_enabled", "").lower() == "true"
-
-    def get_platform_accounts(self, platform, env_values=None):
-        if env_values is None:
-            env_values = dotenv_values(find_dotenv())
-        
-        accounts = set()
-        # 默认账户
-        accounts.add("default")
-        
-        # 查找带有账户名的配置
-        prefix = f"{platform}_"
-        for key in env_values.keys():
-            if key.startswith(prefix) and "_" in key[len(prefix):]:
-                account_name = key[len(prefix):].split("_")[0]
-                if account_name != "default" and account_name != "":
-                    accounts.add(account_name)
-        
-        return sorted(list(accounts))
-
-    def select_account(self, platform, item):
-        if not item:
-            return
-        
-        account = item.data(Qt.UserRole)
-        form = self.platform_pages[platform]["form"]
-        
-        # 清空表单
-        while form.rowCount() > 0:
-            form.removeRow(0)
-        
-        # 读取账户配置
-        env_values = dotenv_values(find_dotenv())
-        
-        # 创建表单项
-        self.field_widgets = {}
-        for field in self.platform_configs[platform]:
-            key = f"{platform}_{account}_{field}" if account != "default" else f"{platform}_{field}"
-            value = env_values.get(key, "")
-            
-            if field == "private":
-                widget = QCheckBox()
-                widget.setChecked(value.lower() != "false")
-            else:
-                widget = QLineEdit()
-                if field in ["token", "password"]:
-                    widget.setEchoMode(QLineEdit.Password)
-                widget.setText(value)
-            
-            self.field_widgets[key] = widget
-            form.addRow(f"{field.capitalize()}:", widget)
-
-    def add_account(self, platform):
-        dialog = AddAccountDialog(platform, self)
-        if dialog.exec_() == QDialog.Accepted:
-            account_data = dialog.get_account_data()
-            account_name = account_data["name"]
-            
-            if not account_name:
-                QMessageBox.warning(self, "Warning", "Account name cannot be empty.")
-                return
-            
-            # 更新.env文件
-            env_file = find_dotenv()
-            if not env_file:
-                env_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
-            
-            for field, value in account_data.items():
-                if field == "name":
-                    continue
-                
-                key = f"{platform}_{account_name}_{field}"
-                if isinstance(value, bool):
-                    value = str(value).lower()
-                
-                set_key(env_file, key, value)
-            
-            # 重新加载设置
-            self.load_settings()
-            
-            # 选择新账户
-            account_list = self.account_lists[platform]
-            for i in range(account_list.count()):
-                if account_list.item(i).data(Qt.UserRole) == account_name:
-                    account_list.setCurrentRow(i)
-                    self.select_account(platform, account_list.item(i))
-                    break
-
-    def delete_account(self, platform):
-        account_list = self.account_lists[platform]
-        current_item = account_list.currentItem()
-        
-        if not current_item:
-            return
-            
-        account = current_item.data(Qt.UserRole)
-        
-        if account == "default":
-            QMessageBox.warning(self, "Warning", "Cannot delete the default account.")
-            return
-        
-        reply = QMessageBox.question(
-            self, 
-            "Confirm Deletion",
-            f"Are you sure you want to delete the account '{account}' for {platform}?",
-            QMessageBox.Yes | QMessageBox.No,
-            QMessageBox.No
-        )
-        
-        if reply == QMessageBox.Yes:
-            # 删除.env中的相关配置
-            env_file = find_dotenv()
-            env_values = dotenv_values(env_file)
-            
-            prefix = f"{platform}_{account}_"
-            keys_to_remove = [k for k in env_values.keys() if k.startswith(prefix)]
-            
-            # 重写.env文件,排除要删除的键
-            with open(env_file, 'w') as f:
-                for k, v in env_values.items():
-                    if k not in keys_to_remove:
-                        f.write(f"{k}={v}\n")
-            
-            # 重新加载设置
-            self.load_settings()
-
-    def enable_account(self, platform):
-        account_list = self.account_lists[platform]
-        current_item = account_list.currentItem()
-        
-        if not current_item:
-            return
-            
-        account = current_item.data(Qt.UserRole)
-        
-        # 读取账户配置
-        env_file = find_dotenv()
-        env_values = dotenv_values(env_file)
-        
-        # 先清除所有账户的启用状态
-        for key in list(env_values.keys()):
-            if key.startswith(f"{platform}_") and key.endswith("_enabled"):
-                del env_values[key]
-        
-        # 设置当前账户为启用
-        if account != "default":
-            set_key(env_file, f"{platform}_{account}_enabled", "true")
-            
-            # 将账户配置复制到默认配置
-            account_config = {}
-            prefix = f"{platform}_{account}_"
-            for key, value in env_values.items():
-                if key.startswith(prefix):
-                    field = key[len(prefix):]
-                    if field != "enabled":  # 不复制enabled标记
-                        account_config[field] = value
-            
-            # 更新默认配置
-            for field, value in account_config.items():
-                default_key = f"{platform}_{field}"
-                set_key(env_file, default_key, value)
-        
-        QMessageBox.information(
-            self, 
-            "Success", 
-            f"Account '{account}' has been enabled for {platform}."
-        )
-        
-        # 重新加载设置
-        self.load_settings()
-
-    def update_account_details(self, platform):
-        account_list = self.account_lists[platform]
-        current_item = account_list.currentItem()
-        
-        if current_item:
-            self.select_account(platform, current_item)
-
-    def save_settings(self):
-        env_file = find_dotenv()
-        if not env_file:
-            env_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
-        
-        # 保存当前显示的账户配置
-        platform = list(self.platform_configs.keys())[self.platform_tabs.currentIndex()]
-        account_list = self.account_lists[platform]
-        current_item = account_list.currentItem()
-        
-        if current_item and hasattr(self, 'field_widgets'):
-            account = current_item.data(Qt.UserRole)
-            
-            for key, widget in self.field_widgets.items():
-                if key.startswith(f"{platform}_{account}"):
-                    if isinstance(widget, QCheckBox):
-                        value = str(widget.isChecked()).lower()
-                    else:
-                        value = widget.text().strip()
-                    
-                    if value:
-                        set_key(env_file, key, value)
-        
-        QMessageBox.information(self, "Success", "Settings saved successfully!")
-        
-        # 如果修改的是启用的账户,更新默认配置
-        if current_item and current_item.data(Qt.UserRole + 1):
-            self.enable_account(platform)
-
-class AboutTab(QWidget):
-    def __init__(self, parent=None):
-        super().__init__(parent)
-        layout = QVBoxLayout()
-        layout.addWidget(QLabel(f"repo_sync tools v{__version__}"))
-        layout.addWidget(QLabel("作者: liuyuqi.gov@msn.cn"))
-        layout.addWidget(QLabel("GitHub: https://github.com/jianboy/repo_sync"))
-        layout.addWidget(QLabel("\n功能说明:"))
-        layout.addWidget(QLabel("- 支持多个代码托管平台"))
-        layout.addWidget(QLabel("- 支持创建/推送/拉取/克隆/删除操作"))
-        layout.addWidget(QLabel("- 自动获取资源管理器当前路径"))
-        layout.addWidget(QLabel("- 配置信息保存在.env文件中"))
-        layout.addWidget(QLabel("- 支持每个平台配置多个账户"))
-        layout.addWidget(QLabel("- 命令行执行结果实时显示"))
-        self.setLayout(layout)
-
-class RepoSyncMainWindow(QTabWidget):
-    def __init__(self):
-        super().__init__()
-        self.setWindowTitle('repo_sync tools v1.12')
-        self.resize(800, 700)
-        self.main_tab = MainTab()
-        self.addTab(self.main_tab, '主界面')
-        self.settings_tab = SettingsTab()
-        self.addTab(self.settings_tab, '设置')
-        self.about_tab = AboutTab()
-        self.addTab(self.about_tab, '关于')
-
-def main():
-    """GUI主入口函数"""
-    if not HAS_QT:
-        print("PyQt5 not installed. Please install it with: pip install PyQt5")
-        print("Running in fallback mode...")
-        # 这里可以添加一个简单的命令行界面作为后备
-        return
-        
-    try:
-        # 确保.env文件存在
-        ensure_env_file()
-        
-        app = QApplication(sys.argv)
-        window = RepoSyncMainWindow()
-        window.show()
-        sys.exit(app.exec_())
-    except Exception as e:
-        print(f"Error starting GUI: {str(e)}")
-        import traceback
-        traceback.print_exc()
-
-if __name__ == '__main__':
-    main() 

+ 203 - 55
repo_sync/gui_main.py → main_gui.py

@@ -5,6 +5,20 @@
 @Time    :   2023/04/27 02:55:59
 @Time    :   2023/04/27 02:55:59
 @License :   Copyright © 2017-2022 liuyuqi. All Rights Reserved.
 @License :   Copyright © 2017-2022 liuyuqi. All Rights Reserved.
 @Desc    :   repo_sync GUI入口
 @Desc    :   repo_sync GUI入口
+
+打包说明:
+使用PyInstaller可以将此应用打包为单个可执行文件:
+1. 安装必要的包: 
+   pip install pyinstaller
+   pip install -e .  # 以开发模式安装当前包
+
+2. 打包命令: 
+   pyinstaller --onefile --windowed --icon=icon.ico --add-data "repo_sync/config.yml;repo_sync" main_gui.py
+   (如果有图标文件,可以通过--icon参数指定)
+
+3. 打包后的可执行文件将位于dist目录中
+
+注意:确保在打包前先安装repo_sync包,这样PyInstaller才能正确找到所有依赖。
 '''
 '''
 import sys
 import sys
 import os
 import os
@@ -16,7 +30,8 @@ try:
         QApplication, QTabWidget, QWidget, QVBoxLayout, QHBoxLayout, QLabel, 
         QApplication, QTabWidget, QWidget, QVBoxLayout, QHBoxLayout, QLabel, 
         QRadioButton, QPushButton, QButtonGroup, QGroupBox, QMessageBox, 
         QRadioButton, QPushButton, QButtonGroup, QGroupBox, QMessageBox, 
         QLineEdit, QScrollArea, QFileDialog, QFormLayout, QCheckBox, QTextEdit,
         QLineEdit, QScrollArea, QFileDialog, QFormLayout, QCheckBox, QTextEdit,
-        QComboBox, QDialog, QDialogButtonBox, QGridLayout, QToolButton, QListWidget, QListWidgetItem
+        QComboBox, QDialog, QDialogButtonBox, QGridLayout, QToolButton, QListWidget, QListWidgetItem,
+        QSpacerItem
     )
     )
     from PyQt5.QtCore import Qt, pyqtSignal, QObject
     from PyQt5.QtCore import Qt, pyqtSignal, QObject
     HAS_QT = True
     HAS_QT = True
@@ -25,7 +40,7 @@ except ImportError:
     print("PyQt5 not installed, GUI mode not available")
     print("PyQt5 not installed, GUI mode not available")
 
 
 # 直接导入repo_sync模块
 # 直接导入repo_sync模块
-sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 try:
 try:
     from repo_sync.repo_sync import RepoSync
     from repo_sync.repo_sync import RepoSync
     from repo_sync.version import __version__
     from repo_sync.version import __version__
@@ -34,8 +49,8 @@ except ImportError:
     print("无法导入repo_sync模块,尝试直接导入...")
     print("无法导入repo_sync模块,尝试直接导入...")
     try:
     try:
         from repo_sync import RepoSync
         from repo_sync import RepoSync
-        from repo_sync.version import __version__
-        from repo_sync.utils.config_reader import ConfigReader
+        from version import __version__
+        from utils.config_reader import ConfigReader
     except ImportError:
     except ImportError:
         print("导入repo_sync模块失败,请确保repo_sync已正确安装")
         print("导入repo_sync模块失败,请确保repo_sync已正确安装")
         __version__ = "未知"
         __version__ = "未知"
@@ -108,7 +123,7 @@ class SettingsTab(QWidget):
             "git.yoq.me": ["username", "token", "private"],
             "git.yoq.me": ["username", "token", "private"],
             "coding": ["username", "token", "project", "private"],
             "coding": ["username", "token", "project", "private"],
             "aliyun": ["compoanyid", "group_id", "username", "token", "private"],
             "aliyun": ["compoanyid", "group_id", "username", "token", "private"],
-            "cnb": ["username", "token", "private"]
+            "cnb": ["group", "username", "token", "private"]
         }
         }
         
         
         # 为每个平台创建标签页
         # 为每个平台创建标签页
@@ -116,12 +131,19 @@ class SettingsTab(QWidget):
         for platform in self.platform_configs.keys():
         for platform in self.platform_configs.keys():
             page = QWidget()
             page = QWidget()
             page_layout = QVBoxLayout()
             page_layout = QVBoxLayout()
+            page_layout.setSpacing(15)  # 增加垂直间距
+            
+            # 创建水平布局,使账户列表和账户详情并排显示
+            accounts_details_layout = QHBoxLayout()
+            accounts_details_layout.setSpacing(20)  # 增加水平间距
             
             
             # 账户列表
             # 账户列表
             account_group = QGroupBox("Accounts")
             account_group = QGroupBox("Accounts")
             account_layout = QVBoxLayout()
             account_layout = QVBoxLayout()
+            account_layout.setSpacing(10)  # 增加垂直间距
             
             
             account_list = QListWidget()
             account_list = QListWidget()
+            account_list.setMinimumWidth(200)  # 设置最小宽度
             self.account_lists[platform] = account_list
             self.account_lists[platform] = account_list
             account_list.currentItemChanged.connect(lambda current, previous, p=platform: self.select_account(p, current))
             account_list.currentItemChanged.connect(lambda current, previous, p=platform: self.select_account(p, current))
             
             
@@ -144,15 +166,30 @@ class SettingsTab(QWidget):
             # 账户详情表单
             # 账户详情表单
             form_group = QGroupBox("Account Details")
             form_group = QGroupBox("Account Details")
             form_layout = QFormLayout()
             form_layout = QFormLayout()
-            form_group.setLayout(form_layout)
+            form_layout.setSpacing(10)  # 增加表单项之间的间距
             
             
-            page_layout.addWidget(account_group)
-            page_layout.addWidget(form_group)
+            # 创建一个垂直布局来组合表单和保存按钮
+            form_container = QVBoxLayout()
+            form_container.addLayout(form_layout)
+            
+            # 添加弹性空间,将保存按钮推到底部
+            form_container.addStretch(1)
             
             
             # 保存按钮
             # 保存按钮
             save_btn = QPushButton("Save Settings")
             save_btn = QPushButton("Save Settings")
             save_btn.clicked.connect(self.save_settings)
             save_btn.clicked.connect(self.save_settings)
-            page_layout.addWidget(save_btn)
+            save_btn.setFixedHeight(30)  # 设置按钮高度
+            save_btn.setMinimumWidth(200)  # 设置最小宽度
+            form_container.addWidget(save_btn)
+            
+            form_group.setLayout(form_container)
+            
+            # 将账户列表和账户详情添加到水平布局中
+            accounts_details_layout.addWidget(account_group, 1)  # 1是伸缩因子
+            accounts_details_layout.addWidget(form_group, 2)     # 2是伸缩因子,使表单区域更宽
+            
+            # 添加水平布局到页面布局
+            page_layout.addLayout(accounts_details_layout)
             
             
             page.setLayout(page_layout)
             page.setLayout(page_layout)
             self.platform_pages[platform] = {
             self.platform_pages[platform] = {
@@ -176,9 +213,16 @@ class SettingsTab(QWidget):
             # 获取该平台的所有账户
             # 获取该平台的所有账户
             accounts = self.config_reader.get_platform_accounts(platform)
             accounts = self.config_reader.get_platform_accounts(platform)
             
             
+            # 获取启用的账户ID
+            enabled_account = str(self.config_reader.config['accounts'].get(platform, {}).get('enable', '1'))
+            
             # 添加到列表
             # 添加到列表
             for account in accounts:
             for account in accounts:
                 item = QListWidgetItem(account)
                 item = QListWidgetItem(account)
+                # 如果是启用的账户,设置背景色以突出显示
+                if account == enabled_account:
+                    item.setBackground(Qt.green)
+                    item.setText(f"{account} (启用中)")
                 account_list.addItem(item)
                 account_list.addItem(item)
             
             
             # 选择第一个账户
             # 选择第一个账户
@@ -187,10 +231,15 @@ class SettingsTab(QWidget):
                 self.select_account(platform, account_list.item(0))
                 self.select_account(platform, account_list.item(0))
 
 
     def select_account(self, platform, item):
     def select_account(self, platform, item):
+
         if not item:
         if not item:
             return
             return
         
         
         account = item.text()
         account = item.text()
+        # 如果账户名包含 "(启用中)" 后缀,去掉后缀
+        if " (启用中)" in account:
+            account = account.split(" (")[0]
+            
         form = self.platform_pages[platform]["form"]
         form = self.platform_pages[platform]["form"]
         
         
         # 清空表单
         # 清空表单
@@ -199,7 +248,6 @@ class SettingsTab(QWidget):
         
         
         # 获取账户配置
         # 获取账户配置
         account_config = self.config_reader.get_account_config(platform, account)
         account_config = self.config_reader.get_account_config(platform, account)
-        
         # 创建表单项
         # 创建表单项
         self.field_widgets = {}
         self.field_widgets = {}
         for field in self.platform_configs[platform]:
         for field in self.platform_configs[platform]:
@@ -261,6 +309,9 @@ class SettingsTab(QWidget):
             return
             return
             
             
         account = current_item.text()
         account = current_item.text()
+        # 如果账户名包含 "(启用中)" 后缀,去掉后缀
+        if " (启用中)" in account:
+            account = account.split(" (")[0]
         
         
         reply = QMessageBox.question(
         reply = QMessageBox.question(
             self, 
             self, 
@@ -292,13 +343,15 @@ class SettingsTab(QWidget):
             return
             return
             
             
         account = current_item.text()
         account = current_item.text()
+        # 如果账户名包含 "(启用中)" 后缀,去掉后缀
+        if " (启用中)" in account:
+            account = account.split(" (")[0]
         
         
         # 更新config.yml文件
         # 更新config.yml文件
         config = self.config_reader.config
         config = self.config_reader.config
         if platform in config['accounts']:
         if platform in config['accounts']:
-            # 将当前账户的配置复制到第一个账户位置
-            account_config = config['accounts'][platform][account]
-            config['accounts'][platform]['1'] = account_config
+            # 将当前账户设置为启用账户
+            config['accounts'][platform]['enable'] = account
             
             
             # 保存配置
             # 保存配置
             with open(self.config_reader.config_path, 'w', encoding='utf-8') as f:
             with open(self.config_reader.config_path, 'w', encoding='utf-8') as f:
@@ -322,6 +375,9 @@ class SettingsTab(QWidget):
         
         
         if current_item and hasattr(self, 'field_widgets'):
         if current_item and hasattr(self, 'field_widgets'):
             account = current_item.text()
             account = current_item.text()
+            # 如果账户名包含 "(启用中)" 后缀,去掉后缀
+            if " (启用中)" in account:
+                account = account.split(" (")[0]
             
             
             # 更新config.yml文件
             # 更新config.yml文件
             config = self.config_reader.config
             config = self.config_reader.config
@@ -355,6 +411,7 @@ class AddAccountDialog(QDialog):
     def init_ui(self):
     def init_ui(self):
         self.setWindowTitle(f"Add {self.platform} Account")
         self.setWindowTitle(f"Add {self.platform} Account")
         layout = QFormLayout()
         layout = QFormLayout()
+        layout.setSpacing(10)  # 增加表单项之间的间距
         
         
         # 账户名称
         # 账户名称
         self.name_edit = QLineEdit()
         self.name_edit = QLineEdit()
@@ -370,7 +427,7 @@ class AddAccountDialog(QDialog):
             "git.yoq.me": ["username", "token", "private"],
             "git.yoq.me": ["username", "token", "private"],
             "coding": ["username", "token", "project", "private"],
             "coding": ["username", "token", "project", "private"],
             "aliyun": ["compoanyid", "group_id", "username", "token", "private"],
             "aliyun": ["compoanyid", "group_id", "username", "token", "private"],
-            "cnb": ["username", "token", "private"]
+            "cnb": ["group", "username", "token", "private"]
         }
         }
         
         
         for field in platform_configs[self.platform]:
         for field in platform_configs[self.platform]:
@@ -385,6 +442,9 @@ class AddAccountDialog(QDialog):
             self.field_widgets[field] = widget
             self.field_widgets[field] = widget
             layout.addRow(f"{field.capitalize()}:", widget)
             layout.addRow(f"{field.capitalize()}:", widget)
         
         
+        # 添加一些空间
+        layout.addItem(QSpacerItem(20, 20))
+        
         # 按钮
         # 按钮
         buttons = QDialogButtonBox(
         buttons = QDialogButtonBox(
             QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
             QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
@@ -394,6 +454,7 @@ class AddAccountDialog(QDialog):
         layout.addRow(buttons)
         layout.addRow(buttons)
         
         
         self.setLayout(layout)
         self.setLayout(layout)
+        self.setMinimumWidth(400)  # 设置对话框最小宽度
     
     
     def get_account_data(self):
     def get_account_data(self):
         data = {"name": self.name_edit.text().strip()}
         data = {"name": self.name_edit.text().strip()}
@@ -427,7 +488,7 @@ class MainTab(QWidget):
         self.op_buttons = QButtonGroup(self)
         self.op_buttons = QButtonGroup(self)
         for i, op in enumerate(["create", "push", "pull", "clone", "delete"]):
         for i, op in enumerate(["create", "push", "pull", "clone", "delete"]):
             btn = QRadioButton(op.capitalize())
             btn = QRadioButton(op.capitalize())
-            if i == 0:
+            if i == 1:
                 btn.setChecked(True)
                 btn.setChecked(True)
             self.op_buttons.addButton(btn, i)
             self.op_buttons.addButton(btn, i)
             op_layout.addWidget(btn)
             op_layout.addWidget(btn)
@@ -485,6 +546,9 @@ class MainTab(QWidget):
         self.command_signals.output.connect(self.update_output)
         self.command_signals.output.connect(self.update_output)
         self.command_signals.finished.connect(self.process_finished)
         self.command_signals.finished.connect(self.process_finished)
         
         
+        # 初始化配置读取器
+        self.config_reader = ConfigReader()
+        
         # 平台变更时更新账户列表
         # 平台变更时更新账户列表
         self.pf_buttons.buttonClicked.connect(self.update_account_list)
         self.pf_buttons.buttonClicked.connect(self.update_account_list)
         self.update_account_list()
         self.update_account_list()
@@ -503,18 +567,18 @@ class MainTab(QWidget):
         accounts = self.get_platform_accounts(platform)
         accounts = self.get_platform_accounts(platform)
         
         
         # 找出启用的账户
         # 找出启用的账户
-        enabled_account = "default"
-        env_values = dotenv_values(find_dotenv())
-        for account in accounts:
-            if account != "default" and env_values.get(f"{platform}_{account}_enabled", "").lower() == "true":
-                enabled_account = account
-                break
+        enabled_account = self.get_enabled_account(platform)
         
         
         # 添加账户到下拉框,启用的账户放在最前面
         # 添加账户到下拉框,启用的账户放在最前面
         if enabled_account in accounts:
         if enabled_account in accounts:
             accounts.remove(enabled_account)
             accounts.remove(enabled_account)
             self.account_combo.addItem(f"{enabled_account} (启用中)")
             self.account_combo.addItem(f"{enabled_account} (启用中)")
             
             
+            # 设置启用账户的背景颜色为绿色
+            model = self.account_combo.model()
+            index = model.index(0, 0)
+            model.setData(index, Qt.green, Qt.BackgroundRole)
+            
         for account in accounts:
         for account in accounts:
             self.account_combo.addItem(account)
             self.account_combo.addItem(account)
         
         
@@ -522,23 +586,33 @@ class MainTab(QWidget):
         self.account_combo.setCurrentIndex(0)
         self.account_combo.setCurrentIndex(0)
 
 
     def get_platform_accounts(self, platform):
     def get_platform_accounts(self, platform):
-        # 读取.env文件中的所有配置
-        env_values = dotenv_values(find_dotenv())
+        # 读取config.yml文件中的所有配置
         accounts = set()
         accounts = set()
         
         
-        # 默认账户
-        accounts.add("default")
+        # 获取该平台的所有账户
+        try:
+            platform_accounts = self.config_reader.get_platform_accounts(platform)
+            for account in platform_accounts:
+                accounts.add(account)
+        except Exception as e:
+            print(f"Error getting platform accounts: {str(e)}")
         
         
-        # 查找带有账户名的配置
-        prefix = f"{platform}_"
-        for key in env_values.keys():
-            if key.startswith(prefix) and "_" in key[len(prefix):]:
-                account_name = key[len(prefix):].split("_")[0]
-                if account_name != "default" and account_name != "":
-                    accounts.add(account_name)
+        # 确保至少有一个默认账户
+        if not accounts:
+            accounts.add("1")
         
         
         return sorted(list(accounts))
         return sorted(list(accounts))
 
 
+    def get_enabled_account(self, platform):
+        """获取平台的启用账户ID"""
+        try:
+            if platform in self.config_reader.config['accounts']:
+                return str(self.config_reader.config['accounts'][platform].get('enable', '1'))
+            return '1'  # 默认返回'1'
+        except Exception as e:
+            print(f"Error getting enabled account: {str(e)}")
+            return '1'  # 出错时默认返回'1'
+
     def run_repo_sync(self):
     def run_repo_sync(self):
         repo_path = self.path_edit.text().strip()
         repo_path = self.path_edit.text().strip()
         if not repo_path:
         if not repo_path:
@@ -558,41 +632,102 @@ class MainTab(QWidget):
         self.result_text.clear()
         self.result_text.clear()
         
         
         # 检查平台配置
         # 检查平台配置
-        load_dotenv()
-        token_key = f"{pf}_{account}_token" if account != "default" else f"{pf}_token"
-        if not os.getenv(token_key):
+        account_config = self.config_reader.get_account_config(pf, account)
+        if not account_config.get('token'):
             QMessageBox.warning(self, "Warning", f"Please configure {pf} token for account '{account}' in Settings tab first.")
             QMessageBox.warning(self, "Warning", f"Please configure {pf} token for account '{account}' in Settings tab first.")
             return
             return
-            
-        # 构建命令
-        cmd = [sys.executable, "-m", "repo_sync"]
-        cmd.append(op)
-        cmd.extend(["-p", pf])
-        cmd.extend(["-repo_path", repo_path])
-        
-        # 如果不是默认账户,需要设置环境变量
-        env = os.environ.copy()
-        if account != "default":
-            # 读取账户配置
-            env_values = dotenv_values(find_dotenv())
-            for key, value in env_values.items():
-                if key.startswith(f"{pf}_{account}_"):
-                    field = key[len(f"{pf}_{account}_"):]
-                    env[f"{pf}_{field}"] = value
+        
+        # 构建参数
+        args = {
+            'command': op,
+            'platform': pf,
+            'repo_path': repo_path
+        }
+        
+        # 获取启用的账户
+        enabled_account = self.get_enabled_account(pf)
+        
+        # 如果不是启用账户,需要设置环境变量
+        if account != enabled_account:
+            # 保存原始环境变量
+            self.original_env = {}
+            for field, value in account_config.items():
+                env_var = f"{pf}_{field}"
+                if env_var in os.environ:
+                    self.original_env[env_var] = os.environ[env_var]
+                os.environ[env_var] = str(value)
+        
+        # 显示执行命令
+        cmd_str = f"repo_sync {op} -p {pf} -repo_path {repo_path}"
+        self.result_text.append(f"Running: {cmd_str}\n")
         
         
         # 执行命令
         # 执行命令
         self.run_btn.setEnabled(False)
         self.run_btn.setEnabled(False)
         self.cancel_btn.setEnabled(True)
         self.cancel_btn.setEnabled(True)
-        self.result_text.append(f"Running: {' '.join(cmd)}\n")
+        
+        # 重定向标准输出和标准错误
+        self.original_stdout = sys.stdout
+        self.original_stderr = sys.stderr
+        sys.stdout = self.OutputRedirector(self.command_signals)
+        sys.stderr = self.OutputRedirector(self.command_signals)
         
         
         # 在新线程中执行命令
         # 在新线程中执行命令
         self.process_thread = threading.Thread(
         self.process_thread = threading.Thread(
-            target=self.run_process,
-            args=(cmd, env)
+            target=self.run_module,
+            args=(args,)
         )
         )
         self.process_thread.daemon = True
         self.process_thread.daemon = True
         self.process_thread.start()
         self.process_thread.start()
 
 
+    def run_module(self, args):
+        try:
+            # 正确导入repo_sync模块的main函数
+            from repo_sync import main
+            self.running = True
+            return_code = 0
+            try:
+                main(args)
+            except SystemExit as e:
+                return_code = e.code if isinstance(e.code, int) else 1
+            except Exception as e:
+                self.command_signals.output.emit(f"Error: {str(e)}")
+                return_code = 1
+            finally:
+                self.command_signals.finished.emit(return_code)
+                # 恢复标准输出和标准错误
+                sys.stdout = self.original_stdout
+                sys.stderr = self.original_stderr
+                # 恢复环境变量
+                if hasattr(self, 'original_env'):
+                    for var, value in self.original_env.items():
+                        os.environ[var] = value
+                    delattr(self, 'original_env')
+        except Exception as e:
+            self.command_signals.output.emit(f"Error: {str(e)}")
+            self.command_signals.finished.emit(1)
+            # 恢复标准输出和标准错误
+            sys.stdout = self.original_stdout
+            sys.stderr = self.original_stderr
+
+    # 输出重定向类
+    class OutputRedirector:
+        def __init__(self, signals):
+            self.signals = signals
+            self.buffer = ""
+        
+        def write(self, text):
+            self.buffer += text
+            if '\n' in self.buffer:
+                lines = self.buffer.split('\n')
+                for line in lines[:-1]:
+                    self.signals.output.emit(line)
+                self.buffer = lines[-1]
+        
+        def flush(self):
+            if self.buffer:
+                self.signals.output.emit(self.buffer)
+                self.buffer = ""
+                
     def run_process(self, cmd, env=None):
     def run_process(self, cmd, env=None):
         try:
         try:
             self.process = subprocess.Popen(
             self.process = subprocess.Popen(
@@ -636,6 +771,19 @@ class MainTab(QWidget):
         if self.process:
         if self.process:
             self.process.terminate()
             self.process.terminate()
             self.result_text.append("\nOperation cancelled by user.")
             self.result_text.append("\nOperation cancelled by user.")
+        else:
+            # 直接调用模式下,通过退出标志通知线程结束
+            self.running = False
+            self.result_text.append("\nOperation cancelled by user.")
+            # 恢复标准输出和标准错误
+            if hasattr(self, 'original_stdout') and hasattr(self, 'original_stderr'):
+                sys.stdout = self.original_stdout
+                sys.stderr = self.original_stderr
+            # 恢复环境变量
+            if hasattr(self, 'original_env'):
+                for var, value in self.original_env.items():
+                    os.environ[var] = value
+                delattr(self, 'original_env')
 
 
 # Explorer路径获取
 # Explorer路径获取
 try:
 try:
@@ -666,7 +814,7 @@ class AboutTab(QWidget):
         layout.addWidget(QLabel("- 支持多个代码托管平台"))
         layout.addWidget(QLabel("- 支持多个代码托管平台"))
         layout.addWidget(QLabel("- 支持创建/推送/拉取/克隆/删除操作"))
         layout.addWidget(QLabel("- 支持创建/推送/拉取/克隆/删除操作"))
         layout.addWidget(QLabel("- 自动获取资源管理器当前路径"))
         layout.addWidget(QLabel("- 自动获取资源管理器当前路径"))
-        layout.addWidget(QLabel("- 配置信息保存在.env文件中"))
+        layout.addWidget(QLabel("- 配置信息保存在config.yml文件中"))
         layout.addWidget(QLabel("- 支持每个平台配置多个账户"))
         layout.addWidget(QLabel("- 支持每个平台配置多个账户"))
         layout.addWidget(QLabel("- 命令行执行结果实时显示"))
         layout.addWidget(QLabel("- 命令行执行结果实时显示"))
         self.setLayout(layout)
         self.setLayout(layout)

+ 7 - 2
repo_sync/__init__.py

@@ -1,13 +1,18 @@
 from .repo_sync import RepoSync
 from .repo_sync import RepoSync
 from .version import __version__
 from .version import __version__
-from .options import parser_args
+from .options import parser_args, only_combine_conf
 from .utils.logger import logger
 from .utils.logger import logger
 import sys
 import sys
 
 
 def main(argv=None):
 def main(argv=None):
     """Main entry point of the program"""
     """Main entry point of the program"""
     try:
     try:
-        args = parser_args()
+        # 如果argv是字典类型,直接使用这些参数
+        if isinstance(argv, dict):
+            args= only_combine_conf(argv)
+        else:
+            args = parser_args(argv)
+            
         if args.get('version'):
         if args.get('version'):
             logger.info(__version__)
             logger.info(__version__)
             sys.exit(0)
             sys.exit(0)

+ 9 - 1
repo_sync/options.py

@@ -57,10 +57,18 @@ def parser_args(overrideArguments=None):
     system_conf.update(command_line_conf)
     system_conf.update(command_line_conf)
     system_conf.update(custom_conf)
     system_conf.update(custom_conf)
     if args.command == None and args.extractor == None:
     if args.command == None and args.extractor == None:
-        raise 'Error, please input cmd and extractor params11'
+        raise 'Error, please input cmd and extractor params'
     return system_conf
     return system_conf
 
 
 
 
+def only_combine_conf(args:dict):
+    system_conf = user_conf = custom_conf = OrderedDict()
+    user_conf = _read_custom_platform_conf("config.yml", args['platform'])
+    system_conf.update(user_conf)
+    system_conf.update(custom_conf)
+    system_conf.update(args)
+    return system_conf
+
 def _read_user_conf() -> OrderedDict:
 def _read_user_conf() -> OrderedDict:
     """读取用户配置文件: .env 文件"""
     """读取用户配置文件: .env 文件"""
     user_conf = OrderedDict()
     user_conf = OrderedDict()

+ 15 - 3
repo_sync/utils/config_reader.py

@@ -31,7 +31,13 @@ class ConfigReader:
             return {}
             return {}
     
     
     def get_platform_config(self, platform: str) -> Dict[str, Any]:
     def get_platform_config(self, platform: str) -> Dict[str, Any]:
-        """Get platform configuration"""
+        """Get platform enable configuration
+        return: {
+                'username': '',
+                'token': '',
+                'private': True
+        }
+        """
         if not self.config or 'accounts' not in self.config:
         if not self.config or 'accounts' not in self.config:
             return {}
             return {}
         
         
@@ -48,7 +54,11 @@ class ConfigReader:
         return {}
         return {}
     
     
     def get_platform_accounts(self, platform: str) -> list:
     def get_platform_accounts(self, platform: str) -> list:
-        """Get all accounts for a platform"""
+        """Get all accounts for a platform
+        return: [
+            
+        ]
+        """
         if not self.config or 'accounts' not in self.config:
         if not self.config or 'accounts' not in self.config:
             return []
             return []
         
         
@@ -69,7 +79,9 @@ class ConfigReader:
         return self.config.get('log', {})
         return self.config.get('log', {})
     
     
     def get_account_config(self, platform: str, account_id: str) -> Dict[str, Any]:
     def get_account_config(self, platform: str, account_id: str) -> Dict[str, Any]:
-        """Get specific account configuration"""
+        """Get specific account configuration
+        
+        """
         if not self.config or 'accounts' not in self.config:
         if not self.config or 'accounts' not in self.config:
             return {}
             return {}
         
         

+ 3 - 2
requirements-win.txt

@@ -1,5 +1,6 @@
-requests==2.27.1
+requests>=2.31.0
 python-dotenv==1.0.0
 python-dotenv==1.0.0
+pyyaml>=6.0.1
+colorama
 PyQt5
 PyQt5
 pywin32
 pywin32
-colorama

+ 40 - 2
setup.py

@@ -1,6 +1,44 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
 from setuptools import setup, find_packages
 from setuptools import setup, find_packages
+import os
+
+# 获取版本号
+version = {}
+with open(os.path.join("repo_sync", "version.py")) as f:
+    exec(f.read(), version)
 
 
 setup(
 setup(
-    name='repo_sync',
-    enter_points={'console_scripts': ['repo_sync = repo_sync:main']},
+    name="repo_sync",
+    version=version.get("__version__", "0.0.1"),
+    description="Repository synchronization tool",
+    author="liuyuqi",
+    author_email="liuyuqi.gov@msn.cn",
+    url="https://github.com/jianboy/repo_sync",
+    packages=find_packages(),
+    install_requires=[
+        "PyYAML",
+        "PyQt5",
+        "pywin32;platform_system=='Windows'",
+    ],
+    entry_points={
+        "console_scripts": [
+            "repo_sync=repo_sync:main",
+        ],
+        "gui_scripts": [
+            "repo_sync_gui=main_gui:main",
+        ],
+    },
+    classifiers=[
+        "Development Status :: 4 - Beta",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: MIT License",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+    ],
+    python_requires=">=3.6",
 )
 )