├── .gitignore
├── README.md
├── app.go
├── build
├── README.md
├── appicon.png
├── darwin
│ ├── Info.dev.plist
│ └── Info.plist
└── windows
│ ├── icon.ico
│ ├── info.json
│ ├── installer
│ ├── project.nsi
│ └── wails_tools.nsh
│ └── wails.exe.manifest
├── common.go
├── frontend
├── index.html
├── package-lock.json
├── package.json
├── package.json.md5
├── src
│ ├── .DS_Store
│ ├── App.css
│ ├── App.tsx
│ ├── assets
│ │ ├── .DS_Store
│ │ └── fonts
│ │ │ ├── OFL.txt
│ │ │ └── nunito-v16-latin-regular.woff2
│ ├── components
│ │ ├── .DS_Store
│ │ ├── block
│ │ │ ├── Index.tsx
│ │ │ └── index.scss
│ │ ├── branch
│ │ │ ├── Index.tsx
│ │ │ ├── Item.tsx
│ │ │ ├── mergeDialog.tsx
│ │ │ └── style.scss
│ │ ├── changes
│ │ │ ├── FileState.tsx
│ │ │ ├── Index.tsx
│ │ │ ├── SubmitView.tsx
│ │ │ └── style.scss
│ │ ├── dialog
│ │ │ ├── About.tsx
│ │ │ ├── Input.tsx
│ │ │ └── Theme.tsx
│ │ ├── diff
│ │ │ ├── DiffCommitView.tsx
│ │ │ ├── DiffWorkView.tsx
│ │ │ ├── FailedInfo.tsx
│ │ │ └── style.scss
│ │ ├── history
│ │ │ ├── Index.tsx
│ │ │ └── Item.tsx
│ │ ├── repoSetting
│ │ │ ├── Action.tsx
│ │ │ ├── Card.tsx
│ │ │ ├── Index.tsx
│ │ │ └── index.scss
│ │ ├── sides
│ │ │ ├── Sides.tsx
│ │ │ └── SidesTop.tsx
│ │ ├── tag
│ │ │ ├── Index.tsx
│ │ │ ├── Item.tsx
│ │ │ └── style.scss
│ │ └── topBar
│ │ │ ├── TopBar.tsx
│ │ │ ├── action
│ │ │ └── Action.tsx
│ │ │ ├── breadcrumb
│ │ │ └── Nav.tsx
│ │ │ └── index.scss
│ ├── config
│ │ ├── app.ts
│ │ └── gitCmd.ts
│ ├── main.tsx
│ ├── nativeMenuEvents.ts
│ ├── store
│ │ ├── index.ts
│ │ ├── sliceCategory.ts
│ │ ├── sliceCommitDiff.ts
│ │ ├── sliceMain.ts
│ │ ├── sliceSetting.ts
│ │ └── sliceWorkDiff.ts
│ ├── style.css
│ ├── utils
│ │ ├── common.ts
│ │ ├── notification.tsx
│ │ └── repo.ts
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
├── wailsjs
│ ├── .DS_Store
│ ├── go
│ │ ├── main
│ │ │ ├── App.d.ts
│ │ │ └── App.js
│ │ ├── models.ts
│ │ └── repository
│ │ │ ├── Repository.d.ts
│ │ │ └── Repository.js
│ └── runtime
│ │ ├── package.json
│ │ ├── runtime.d.ts
│ │ └── runtime.js
├── yarn-error.log
└── yarn.lock
├── go.mod
├── go.sum
├── main.go
├── repository
├── branch.go
├── commit.go
├── diffCommit.go
├── diffWork.go
├── merge.go
├── mian.go
├── tag.go
├── utils.go
└── workTree.go
├── screenshots
├── branch.png
├── change.png
├── history.png
└── tag.png
├── utils
└── common.go
└── wails.json
/.gitignore:
--------------------------------------------------------------------------------
1 | build/bin
2 | node_modules
3 | frontend/dist
4 | .idea
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Table of Contents
2 | - [Introduction](#Introduction)
3 | - [Features](#Features)
4 | - [Installation](#Installation)
5 | - [Usage](#Usage)
6 | - [Screenshots](#Screenshots)
7 | - [Contribution](#Contribution)
8 | - [License](#License)
9 |
10 | ## Introduction
11 | git-helper is an open-source software that aims to assist developers in using git more conveniently and efficiently. In some ways, it can also serve as a git GUI, allowing for quick management of multiple git repositories and grouping them for easy organization. When a specific repository is selected, the corresponding repository directory and command line can be quickly opened, and features such as branch and tag management, code committing and merging can be done.
12 |
13 |
14 | ## Features
15 |
16 | - Friendly classification of git repositories
17 | - Convenient code committing and merging
18 | - Viewing commit history
19 | - Branch management, including viewing, adding and deleting branches
20 | - Tag management, including viewing, adding and deleting tags
21 | - Switching between preferred color themes
22 |
23 | ## Installation
24 |
25 | - Clone the source code and compile it.
26 | - Download the binary file from the release page.
27 |
28 | Note: Many features have only been tested on mac os, Although there are no destructive git commands, but this project is currently only provided for learning and research, please do not use it formally.
29 |
30 | ## Usage
31 | Currently, there is no clone operation, so you can add your git repositories through the menu first. git-helper will record the repository's path and generate a repository alias, which will be placed in the default category. You can subsequently add categories to classify and sort these repositories. When you select a repository, any actions you perform will be executed in that repository's path. Therefore, when you delete the repository in git-helper, it only removes the path and does not make any destructive changes to your original repository.
32 |
33 | ## Screenshots
34 |
35 | 
36 | 
37 | 
38 | 
39 |
40 | ## Contribution
41 | Feel free to open issues or pull requests if you have any suggestions or found any bugs.
42 |
43 | ## License
44 | This project is licensed under the MIT License.
--------------------------------------------------------------------------------
/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "git-helper/repository"
6 | "git-helper/utils"
7 | "github.com/wailsapp/wails/v2/pkg/menu"
8 | "github.com/wailsapp/wails/v2/pkg/menu/keys"
9 | "github.com/wailsapp/wails/v2/pkg/runtime"
10 | "os/user"
11 | )
12 |
13 | // App struct
14 | type App struct {
15 | ctx context.Context
16 | repository *repository.Repository
17 | dataSaveJson string
18 | }
19 |
20 | // NewApp creates a new App application struct
21 | func NewApp() *App {
22 | return &App{}
23 | }
24 |
25 | // startup is called when the app starts. The context is saved
26 | // so we can call the runtime methods
27 | func (a *App) startup(ctx context.Context) {
28 | a.ctx = ctx
29 | u, err := user.Current()
30 | if err != nil {
31 | panic(err)
32 | }
33 | a.dataSaveJson = u.HomeDir + "/Git_Helper.json"
34 | }
35 | func (a *App) setRepository(r *repository.Repository) {
36 | a.repository = r
37 | }
38 |
39 | // Greet returns a greeting for the given name
40 | func (a *App) menu() *menu.Menu {
41 | appMenu := menu.NewMenu()
42 |
43 | appMenu.Append(menu.EditMenu())
44 |
45 | repo := appMenu.AddSubmenu("Repository")
46 | repo.AddText("Add local repository", keys.CmdOrCtrl("r"), func(_ *menu.CallbackData) {
47 | path, err := runtime.OpenDirectoryDialog(a.ctx, runtime.OpenDialogOptions{
48 | Title: "Select Local Git Repository",
49 | })
50 | if err != nil || len(path) == 0 {
51 | return
52 | }
53 | id := utils.Sha256(path)
54 | runtime.EventsEmit(a.ctx, "Repository_A", id, path)
55 | })
56 |
57 | repo.AddText("Repository manage", keys.CmdOrCtrl("m"), func(_ *menu.CallbackData) {
58 | runtime.EventsEmit(a.ctx, "Repository_M")
59 | })
60 |
61 | bt := appMenu.AddSubmenu("Branch&Tag")
62 |
63 | bt.AddText("Branch manage", keys.CmdOrCtrl("b"), func(_ *menu.CallbackData) {
64 | runtime.EventsEmit(a.ctx, "Branch_M")
65 | })
66 | bt.AddText("Tag manage", keys.CmdOrCtrl("t"), func(_ *menu.CallbackData) {
67 | runtime.EventsEmit(a.ctx, "Tag_M")
68 | })
69 |
70 | other := appMenu.AddSubmenu("Other")
71 | other.AddText("Theme", keys.CmdOrCtrl(""), func(_ *menu.CallbackData) {
72 | runtime.EventsEmit(a.ctx, "Settings_Theme")
73 | })
74 | other.AddText("About", keys.CmdOrCtrl(""), func(_ *menu.CallbackData) {
75 | runtime.EventsEmit(a.ctx, "Settings_About")
76 | })
77 |
78 | return appMenu
79 | }
80 |
--------------------------------------------------------------------------------
/build/README.md:
--------------------------------------------------------------------------------
1 | # Build Directory
2 |
3 | The build directory is used to house all the build files and assets for your application.
4 |
5 | The structure is:
6 |
7 | * bin - Output directory
8 | * darwin - macOS specific files
9 | * windows - Windows specific files
10 |
11 | ## Mac
12 |
13 | The `darwin` directory holds files specific to Mac builds.
14 | These may be customised and used as part of the build. To return these files to the default state, simply delete them
15 | and
16 | build with `wails build`.
17 |
18 | The directory contains the following files:
19 |
20 | - `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`.
21 | - `Info.dev.plist` - same as the main plist file but used when building using `wails dev`.
22 |
23 | ## Windows
24 |
25 | The `windows` directory contains the manifest and rc files used when building with `wails build`.
26 | These may be customised for your application. To return these files to the default state, simply delete them and
27 | build with `wails build`.
28 |
29 | - `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to
30 | use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file
31 | will be created using the `appicon.png` file in the build directory.
32 | - `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`.
33 | - `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer,
34 | as well as the application itself (right click the exe -> properties -> details)
35 | - `wails.exe.manifest` - The main application manifest file.
--------------------------------------------------------------------------------
/build/appicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xusenlin/git-helper/19e88fa5f00ec16a87de17963883de7ddfc176dc/build/appicon.png
--------------------------------------------------------------------------------
/build/darwin/Info.dev.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundlePackageType
5 | APPL
6 | CFBundleName
7 | {{.Info.ProductName}}
8 | CFBundleExecutable
9 | {{.Name}}
10 | CFBundleIdentifier
11 | com.wails.{{.Name}}
12 | CFBundleVersion
13 | {{.Info.ProductVersion}}
14 | CFBundleGetInfoString
15 | {{.Info.Comments}}
16 | CFBundleShortVersionString
17 | {{.Info.ProductVersion}}
18 | CFBundleIconFile
19 | iconfile
20 | LSMinimumSystemVersion
21 | 10.13.0
22 | NSHighResolutionCapable
23 | true
24 | NSHumanReadableCopyright
25 | {{.Info.Copyright}}
26 | NSAppTransportSecurity
27 |
28 | NSAllowsLocalNetworking
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/build/darwin/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundlePackageType
5 | APPL
6 | CFBundleName
7 | {{.Info.ProductName}}
8 | CFBundleExecutable
9 | {{.Name}}
10 | CFBundleIdentifier
11 | com.wails.{{.Name}}
12 | CFBundleVersion
13 | {{.Info.ProductVersion}}
14 | CFBundleGetInfoString
15 | {{.Info.Comments}}
16 | CFBundleShortVersionString
17 | {{.Info.ProductVersion}}
18 | CFBundleIconFile
19 | iconfile
20 | LSMinimumSystemVersion
21 | 10.13.0
22 | NSHighResolutionCapable
23 | true
24 | NSHumanReadableCopyright
25 | {{.Info.Copyright}}
26 |
27 |
--------------------------------------------------------------------------------
/build/windows/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xusenlin/git-helper/19e88fa5f00ec16a87de17963883de7ddfc176dc/build/windows/icon.ico
--------------------------------------------------------------------------------
/build/windows/info.json:
--------------------------------------------------------------------------------
1 | {
2 | "fixed": {
3 | "file_version": "{{.Info.ProductVersion}}"
4 | },
5 | "info": {
6 | "0000": {
7 | "ProductVersion": "{{.Info.ProductVersion}}",
8 | "CompanyName": "{{.Info.CompanyName}}",
9 | "FileDescription": "{{.Info.ProductName}}",
10 | "LegalCopyright": "{{.Info.Copyright}}",
11 | "ProductName": "{{.Info.ProductName}}",
12 | "Comments": "{{.Info.Comments}}"
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/build/windows/installer/project.nsi:
--------------------------------------------------------------------------------
1 | Unicode true
2 |
3 | ####
4 | ## Please note: Template replacements don't work in this file. They are provided with default defines like
5 | ## mentioned underneath.
6 | ## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo.
7 | ## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually
8 | ## from outside of Wails for debugging and development of the installer.
9 | ##
10 | ## For development first make a wails nsis build to populate the "wails_tools.nsh":
11 | ## > wails build --target windows/amd64 --nsis
12 | ## Then you can call makensis on this file with specifying the path to your binary:
13 | ## For a AMD64 only installer:
14 | ## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe
15 | ## For a ARM64 only installer:
16 | ## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe
17 | ## For a installer with both architectures:
18 | ## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe
19 | ####
20 | ## The following information is taken from the ProjectInfo file, but they can be overwritten here.
21 | ####
22 | ## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}"
23 | ## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}"
24 | ## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}"
25 | ## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}"
26 | ## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}"
27 | ###
28 | ## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe"
29 | ## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
30 | ####
31 | ## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html
32 | ####
33 | ## Include the wails tools
34 | ####
35 | !include "wails_tools.nsh"
36 |
37 | # The version information for this two must consist of 4 parts
38 | VIProductVersion "${INFO_PRODUCTVERSION}.0"
39 | VIFileVersion "${INFO_PRODUCTVERSION}.0"
40 |
41 | VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}"
42 | VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer"
43 | VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}"
44 | VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}"
45 | VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}"
46 | VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}"
47 |
48 | !include "MUI.nsh"
49 |
50 | !define MUI_ICON "..\icon.ico"
51 | !define MUI_UNICON "..\icon.ico"
52 | # !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314
53 | !define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps
54 | !define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
55 |
56 | !insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
57 | # !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer
58 | !insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
59 | !insertmacro MUI_PAGE_INSTFILES # Installing page.
60 | !insertmacro MUI_PAGE_FINISH # Finished installation page.
61 |
62 | !insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page
63 |
64 | !insertmacro MUI_LANGUAGE "English" # Set the Language of the installer
65 |
66 | ## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1
67 | #!uninstfinalize 'signtool --file "%1"'
68 | #!finalize 'signtool --file "%1"'
69 |
70 | Name "${INFO_PRODUCTNAME}"
71 | OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file.
72 | InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder).
73 | ShowInstDetails show # This will always show the installation details.
74 |
75 | Function .onInit
76 | !insertmacro wails.checkArchitecture
77 | FunctionEnd
78 |
79 | Section
80 | !insertmacro wails.webview2runtime
81 |
82 | SetOutPath $INSTDIR
83 |
84 | !insertmacro wails.files
85 |
86 | CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
87 | CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
88 |
89 | !insertmacro wails.writeUninstaller
90 | SectionEnd
91 |
92 | Section "uninstall"
93 | RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
94 |
95 | RMDir /r $INSTDIR
96 |
97 | Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
98 | Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
99 |
100 | !insertmacro wails.deleteUninstaller
101 | SectionEnd
102 |
--------------------------------------------------------------------------------
/build/windows/installer/wails_tools.nsh:
--------------------------------------------------------------------------------
1 | # DO NOT EDIT - Generated automatically by `wails build`
2 |
3 | !include "x64.nsh"
4 | !include "WinVer.nsh"
5 | !include "FileFunc.nsh"
6 |
7 | !ifndef INFO_PROJECTNAME
8 | !define INFO_PROJECTNAME "{{.Name}}"
9 | !endif
10 | !ifndef INFO_COMPANYNAME
11 | !define INFO_COMPANYNAME "{{.Info.CompanyName}}"
12 | !endif
13 | !ifndef INFO_PRODUCTNAME
14 | !define INFO_PRODUCTNAME "{{.Info.ProductName}}"
15 | !endif
16 | !ifndef INFO_PRODUCTVERSION
17 | !define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}"
18 | !endif
19 | !ifndef INFO_COPYRIGHT
20 | !define INFO_COPYRIGHT "{{.Info.Copyright}}"
21 | !endif
22 | !ifndef PRODUCT_EXECUTABLE
23 | !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
24 | !endif
25 | !ifndef UNINST_KEY_NAME
26 | !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
27 | !endif
28 | !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}"
29 |
30 | !ifndef REQUEST_EXECUTION_LEVEL
31 | !define REQUEST_EXECUTION_LEVEL "admin"
32 | !endif
33 |
34 | RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
35 |
36 | !ifdef ARG_WAILS_AMD64_BINARY
37 | !define SUPPORTS_AMD64
38 | !endif
39 |
40 | !ifdef ARG_WAILS_ARM64_BINARY
41 | !define SUPPORTS_ARM64
42 | !endif
43 |
44 | !ifdef SUPPORTS_AMD64
45 | !ifdef SUPPORTS_ARM64
46 | !define ARCH "amd64_arm64"
47 | !else
48 | !define ARCH "amd64"
49 | !endif
50 | !else
51 | !ifdef SUPPORTS_ARM64
52 | !define ARCH "arm64"
53 | !else
54 | !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY"
55 | !endif
56 | !endif
57 |
58 | !macro wails.checkArchitecture
59 | !ifndef WAILS_WIN10_REQUIRED
60 | !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later."
61 | !endif
62 |
63 | !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED
64 | !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}"
65 | !endif
66 |
67 | ${If} ${AtLeastWin10}
68 | !ifdef SUPPORTS_AMD64
69 | ${if} ${IsNativeAMD64}
70 | Goto ok
71 | ${EndIf}
72 | !endif
73 |
74 | !ifdef SUPPORTS_ARM64
75 | ${if} ${IsNativeARM64}
76 | Goto ok
77 | ${EndIf}
78 | !endif
79 |
80 | IfSilent silentArch notSilentArch
81 | silentArch:
82 | SetErrorLevel 65
83 | Abort
84 | notSilentArch:
85 | MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}"
86 | Quit
87 | ${else}
88 | IfSilent silentWin notSilentWin
89 | silentWin:
90 | SetErrorLevel 64
91 | Abort
92 | notSilentWin:
93 | MessageBox MB_OK "${WAILS_WIN10_REQUIRED}"
94 | Quit
95 | ${EndIf}
96 |
97 | ok:
98 | !macroend
99 |
100 | !macro wails.files
101 | !ifdef SUPPORTS_AMD64
102 | ${if} ${IsNativeAMD64}
103 | File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}"
104 | ${EndIf}
105 | !endif
106 |
107 | !ifdef SUPPORTS_ARM64
108 | ${if} ${IsNativeARM64}
109 | File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}"
110 | ${EndIf}
111 | !endif
112 | !macroend
113 |
114 | !macro wails.writeUninstaller
115 | WriteUninstaller "$INSTDIR\uninstall.exe"
116 |
117 | SetRegView 64
118 | WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
119 | WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
120 | WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
121 | WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
122 | WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
123 | WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
124 |
125 | ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
126 | IntFmt $0 "0x%08X" $0
127 | WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
128 | !macroend
129 |
130 | !macro wails.deleteUninstaller
131 | Delete "$INSTDIR\uninstall.exe"
132 |
133 | SetRegView 64
134 | DeleteRegKey HKLM "${UNINST_KEY}"
135 | !macroend
136 |
137 | # Install webview2 by launching the bootstrapper
138 | # See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment
139 | !macro wails.webview2runtime
140 | !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT
141 | !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime"
142 | !endif
143 |
144 | SetRegView 64
145 | # If the admin key exists and is not empty then webview2 is already installed
146 | ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
147 | ${If} $0 != ""
148 | Goto ok
149 | ${EndIf}
150 |
151 | ${If} ${REQUEST_EXECUTION_LEVEL} == "user"
152 | # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed
153 | ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
154 | ${If} $0 != ""
155 | Goto ok
156 | ${EndIf}
157 | ${EndIf}
158 |
159 | SetDetailsPrint both
160 | DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}"
161 | SetDetailsPrint listonly
162 |
163 | InitPluginsDir
164 | CreateDirectory "$pluginsdir\webview2bootstrapper"
165 | SetOutPath "$pluginsdir\webview2bootstrapper"
166 | File "tmp\MicrosoftEdgeWebview2Setup.exe"
167 | ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'
168 |
169 | SetDetailsPrint both
170 | ok:
171 | !macroend
--------------------------------------------------------------------------------
/build/windows/wails.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | true/pm
12 | permonitorv2,permonitor
13 |
14 |
15 |
--------------------------------------------------------------------------------
/common.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "git-helper/utils"
6 | "github.com/atotto/clipboard"
7 | "github.com/wailsapp/wails/v2/pkg/runtime"
8 | "io/ioutil"
9 | )
10 |
11 | func (a *App) Sha256(s string) string {
12 | return utils.Sha256(s)
13 | }
14 | func (a *App) MessageDialog(title string, message string) {
15 | _, _ = runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
16 | Type: runtime.InfoDialog,
17 | Title: title,
18 | Message: message,
19 | })
20 | }
21 | func (a *App) SaveJsonFile(t string) error {
22 |
23 | if err := utils.RemoveFile(a.dataSaveJson); err != nil {
24 | return err
25 | }
26 | err := ioutil.WriteFile(a.dataSaveJson, []byte(t), 0666)
27 |
28 | if err != nil {
29 | return err
30 | }
31 | return nil
32 | }
33 | func (a *App) ReadJsonFile() (string, error) {
34 |
35 | b, err := ioutil.ReadFile(a.dataSaveJson)
36 | if err != nil {
37 | return "", err
38 | }
39 | return string(b), nil
40 | }
41 | func (a *App) Clipboard(t string) error {
42 | return clipboard.WriteAll(t)
43 | }
44 | func (a *App) IsGitRepository(path string) (bool, error) {
45 | if !utils.IsDir(path + "/.git") {
46 | return false, errors.New("not a git repository")
47 | }
48 | return true, nil
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | GitUI
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@ant-design/icons": "^4.8.0",
13 | "@reduxjs/toolkit": "^1.9.0",
14 | "@types/react-beautiful-dnd": "^13.1.2",
15 | "antd": "^5.0.1",
16 | "dayjs": "^1.11.7",
17 | "react": "^18.2.0",
18 | "react-beautiful-dnd": "^13.1.1",
19 | "react-dom": "^18.2.0",
20 | "react-redux": "^8.0.5",
21 | "sass": "^1.56.1"
22 | },
23 | "devDependencies": {
24 | "@types/react": "^18.0.17",
25 | "@types/react-dom": "^18.0.6",
26 | "@vitejs/plugin-react": "^2.0.1",
27 | "typescript": "^4.6.4",
28 | "vite": "^3.0.7"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/frontend/package.json.md5:
--------------------------------------------------------------------------------
1 | 657141f18b8a91280bdcc9a27bb1581a
--------------------------------------------------------------------------------
/frontend/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xusenlin/git-helper/19e88fa5f00ec16a87de17963883de7ddfc176dc/frontend/src/.DS_Store
--------------------------------------------------------------------------------
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | .app {
2 | height: 100vh;
3 | width: 100%;
4 | display: flex;
5 | }
6 |
7 | .sides {
8 | width: 24%;
9 | min-width: 240px;
10 | overflow: hidden;
11 | background-color: #dfe3e3;
12 | height: 100%;
13 | }
14 |
15 | .sides-top {
16 | width: 100%;
17 | height: 60px;
18 | display: flex;
19 | align-items: center;
20 | padding: 0 20px;
21 | justify-content: flex-end;
22 | --wails-draggable: drag;
23 | }
24 | .sides-content{
25 | overflow-y: auto;
26 | height: calc(100vh - 60px);
27 | }
28 |
29 | .theme-icon {
30 | width: 15px;
31 | position: relative;
32 | top: 1px;
33 | cursor: pointer;
34 | opacity: 0.45
35 | }
36 |
37 | .container {
38 | flex: 1;
39 | height: 100%;
40 | position: relative;
41 | background-color: #fff;
42 | }
43 |
44 | .main {
45 | display: flex;
46 | width: 100%;
47 | position: relative;
48 | height: calc(100vh - 60px);
49 | justify-content: center;
50 | align-items: center
51 | }
52 |
53 | .main .ant-tabs-nav {
54 | margin: 0;
55 | }
56 |
57 | .left {
58 | width: 300px;
59 | min-width: 300px;
60 | max-width: 360px;
61 | height: 100%;
62 | border-right: 1px solid rgba(5, 5, 5, 0.06);
63 | }
64 |
65 | .right {
66 | flex: 1;
67 | height: 100%;
68 | overflow-y: auto;
69 | }
70 |
--------------------------------------------------------------------------------
/frontend/src/App.tsx:
--------------------------------------------------------------------------------
1 | import './App.css';
2 | import {State} from "./store";
3 | import {Empty, ConfigProvider, Tabs} from "antd"
4 | import Sides from "./components/sides/Sides"
5 | import SidesTop from "./components/sides/SidesTop"
6 | import Changes from "./components/changes/Index"
7 | import History from "./components/history/Index"
8 | import Tag from "./components/tag/Index"
9 | import Theme from "./components/dialog/Theme"
10 | import About from "./components/dialog/About"
11 | import Branch from "./components/branch/Index"
12 | import TopBar from "./components/topBar/TopBar"
13 | import DiffWorkView from "./components/diff/DiffWorkView"
14 | import DiffCommitView from "./components/diff/DiffCommitView"
15 | import {useSelector} from "react-redux";
16 | import {useState} from "react";
17 |
18 |
19 | function App() {
20 | const main = useSelector((state: State) => state.main);
21 | const themeColor = useSelector((state: State) => state.setting.themeColor);
22 | const [activeTab,setActiveTab] = useState("1")
23 |
24 |
25 | const content = <>
26 |
27 | {setActiveTab(k)}}
32 | items={[
33 | {
34 | label: `Changes`,
35 | key: "1",
36 | children: ,
37 | },
38 | {
39 | label: `History`,
40 | key: "2",
41 | children: ,
42 | }
43 | ]}
44 | />
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | >
55 |
56 | const empty =
57 |
58 |
59 |
60 | return (
61 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | {main.selectedRepositoryId && main.selectedRepositoryBranch ? content : empty}
73 |
74 |
75 | <>
76 |
77 |
78 |
79 |
80 | >
81 |
82 |
83 | )
84 | }
85 |
86 | export default App
87 |
--------------------------------------------------------------------------------
/frontend/src/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xusenlin/git-helper/19e88fa5f00ec16a87de17963883de7ddfc176dc/frontend/src/assets/.DS_Store
--------------------------------------------------------------------------------
/frontend/src/assets/fonts/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com),
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xusenlin/git-helper/19e88fa5f00ec16a87de17963883de7ddfc176dc/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2
--------------------------------------------------------------------------------
/frontend/src/components/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xusenlin/git-helper/19e88fa5f00ec16a87de17963883de7ddfc176dc/frontend/src/components/.DS_Store
--------------------------------------------------------------------------------
/frontend/src/components/block/Index.tsx:
--------------------------------------------------------------------------------
1 | import "./index.scss"
2 | import {ReactNode} from "react"
3 |
4 | type propType = {
5 | title?: string|ReactNode
6 | children: ReactNode
7 | bottom?: ReactNode
8 | action?:ReactNode
9 | }
10 |
11 | const Block = (props: propType) => {
12 | return (
13 |
14 | { props.action || props.title ?
15 |
{props.title}
16 |
{props.action}
17 |
:'' }
18 |
19 |
20 |
21 | {props.children}
22 |
23 |
24 | {props.bottom}
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | export default Block;
32 |
--------------------------------------------------------------------------------
/frontend/src/components/block/index.scss:
--------------------------------------------------------------------------------
1 | .main-block{
2 | //flex: 1;
3 | height: calc(100vh - 106px);
4 | //border: 1px solid #dfe3e3;
5 | //margin: 6px;
6 | //border-radius: 8px;
7 | overflow: hidden;
8 | .top-title{
9 | display: flex;
10 | align-items: center;
11 | width: 100%;
12 | padding: 0 12px;
13 | font-size: 14px;
14 | justify-content: space-between;
15 | height: 40px;
16 | background: #f2f6f6;
17 | }
18 | .main-block-content{
19 | display: flex;
20 | flex-direction: column;
21 | .top{
22 | flex: 1;
23 | overflow-x: hidden;
24 | overflow-y: auto;
25 | }
26 | .bottom{
27 |
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/frontend/src/components/branch/Index.tsx:
--------------------------------------------------------------------------------
1 | import "./style.scss"
2 | import Item from "./Item"
3 | import {State} from "../../store";
4 | import React, {useState, useRef, useMemo, useEffect} from "react";
5 | import {useDispatch, useSelector} from "react-redux";
6 | import {Button, Input, Space, Drawer, Empty} from "antd";
7 | import {AddBranch, GetAllBranch, GetLocalBranch} from "../../../wailsjs/go/repository/Repository"
8 | import {repository} from "../../../wailsjs/go/models"
9 | import {setOpenRepositoryBranch} from "../../store/sliceSetting";
10 | import {warning} from "../../utils/common"
11 | import MergeDialog from "./mergeDialog"
12 | import {setAllBranch} from "../../store/sliceMain";
13 |
14 |
15 | const Branch = () => {
16 | const limit = 20
17 |
18 | const dispatch = useDispatch();
19 | // const [branch,setBranch] = useState([]);
20 | const branch = useSelector((state: State) => state.main.currentlyRepositoryLocalBranch);
21 | const selectedRepositoryId = useSelector((state: State) => state.main.selectedRepositoryId);
22 | const showRepositoryBranch = useSelector((state: State) => state.setting.showRepositoryBranch);
23 | const mergeDialogComponentRef = useRef<{ OpenMergeDialog: (branchName: string) => void }>(null);
24 | const [branchName, setBranchName] = useState("")
25 |
26 | const [keyword,setKeyword] = useState("")
27 | const computedBranch = useMemo(() => (branch.filter(r=>r.name.indexOf(keyword)!==-1)), [keyword,branch]);
28 |
29 | // const getAllBranch = () => {
30 | // if(!selectedRepositoryId){
31 | // return
32 | // }
33 | // GetAllBranch().then(b=>{
34 | // setBranch(b||[])
35 | // }).catch(e=>{
36 | // warning(JSON.stringify(e))
37 | // })
38 | // }
39 | // useEffect(() => {
40 | // getAllBranch()
41 | // }, [selectedRepositoryId]);
42 |
43 |
44 | const addBranch = async () => {
45 | try {
46 | await AddBranch(branchName)
47 | const b = await GetLocalBranch()
48 | dispatch(setAllBranch(b))
49 | setBranchName("")
50 | } catch (e) {
51 | console.log(e)
52 | warning("Branch:" + JSON.stringify(e))
53 | }
54 | }
55 |
56 | const onCloseBranch = () => {
57 | dispatch(setOpenRepositoryBranch(false))
58 | }
59 |
60 |
61 | const bottom =
62 |
63 | ) => {
64 | setBranchName(e.target.value)
65 | }}/>
66 | {
67 | await addBranch()
68 | }}>Add New Branch
69 |
70 |
71 |
72 | const content = <>
73 |
74 | ) => {
75 | setKeyword(e.target.value)
76 | }} />
77 | {(computedBranch.length > limit ? computedBranch.slice(0, limit) : computedBranch).map(r => - {
78 | mergeDialogComponentRef.current?.OpenMergeDialog(r.name)
79 | }} b={r} key={r.name}/>)}
80 |
81 |
82 | {bottom}
83 |
84 | >
85 |
86 | return (
87 | Total:{branch.length}}
90 | bodyStyle={{display: 'flex', flexDirection: "column", padding: 0}}
91 | placement="right"
92 | onClose={onCloseBranch}
93 | destroyOnClose={true}
94 | open={showRepositoryBranch}
95 | >
96 |
97 | {selectedRepositoryId ? content :
98 | }
99 |
100 | );
101 | };
102 |
103 | export default Branch;
104 |
--------------------------------------------------------------------------------
/frontend/src/components/branch/Item.tsx:
--------------------------------------------------------------------------------
1 | import {Card, Modal, Space, Checkbox, Badge, message} from "antd"
2 | import dayjs from "dayjs"
3 | import relativeTime from 'dayjs/plugin/relativeTime';
4 | import {warning, success, clipboard} from "../../utils/common";
5 | import {repository} from "../../../wailsjs/go/models"
6 | import {DelBranch, GetLocalBranch, PushBranch} from "../../../wailsjs/go/repository/Repository"
7 | import {DeleteOutlined, SnippetsOutlined, ArrowLeftOutlined, ArrowUpOutlined} from "@ant-design/icons"
8 | import {setAllBranch} from "../../store/sliceMain";
9 | import {useDispatch, useSelector} from "react-redux";
10 | import {mainBranch} from "../../config/app";
11 | import {State} from "../../store";
12 |
13 | dayjs.extend(relativeTime)
14 |
15 |
16 | const Item = (props: { b: repository.Branch,merge:()=>void }) => {
17 | const sledBranch = useSelector((state: State) => state.main.selectedRepositoryBranch);
18 | const [messageApi, contextHolder] = message.useMessage();
19 |
20 | const dispatch = useDispatch();
21 |
22 | const delBranch = (name: string) => {
23 | Modal.warning({
24 | closable: true,
25 | title: 'Confirm message',
26 | content:
27 |
Are you sure you want to delete this branch?
28 |
Delete the remote branch at the same time.
29 |
,
30 | onOk() {
31 | const checkbox = document.getElementById("delRemoteBranchCheckbox") as HTMLInputElement
32 | DelBranch(name,checkbox.checked).then(out => {
33 | success(out);
34 | return GetLocalBranch()
35 | }).then(b => {
36 | dispatch(setAllBranch(b))
37 | }).catch(e => {
38 | warning(JSON.stringify(e))
39 | })
40 | }
41 | });
42 | }
43 |
44 | const merge = async () => {
45 | if(!sledBranch){
46 | warning("please select a branch first")
47 | return
48 | }
49 | props.merge()
50 | }
51 |
52 | const pushBranch = (name:string) => {
53 | messageApi.open({
54 | type: 'loading',
55 | content: 'Pushing...',
56 | duration: 0,
57 | });
58 | PushBranch(name).then(out=>{
59 | success(out)
60 | return GetLocalBranch()
61 | }).then(b => {
62 | dispatch(setAllBranch(b))
63 | }).catch(e=>{
64 | warning(e)
65 | }).finally(messageApi.destroy)
66 | }
67 |
68 |
69 | const extra =
70 | { !props.b.upstream && {
71 | pushBranch(props.b.name)
72 | }} style={{cursor: "pointer", opacity: 0.45}}/> }
73 | {
74 | (sledBranch != props.b.name ) &&
75 | }
76 | {
77 | (mainBranch != props.b.name) && (sledBranch != props.b.name ) &&
78 | {
79 | delBranch(props.b.name)
80 | }} style={{cursor: "pointer", opacity: 0.45}}/>
81 | }
82 |
83 |
84 | return (
85 | }
88 | extra={extra} style={{marginBottom: 10}}>
89 |
90 |
{props.b.refName}
91 |
92 |
Hash:{props.b.hash.substring(0, 7)}
93 |
{
94 | await clipboard(props.b.hash)
95 | }} style={{cursor: "pointer", opacity: 0.65}}/>
96 |
97 |
98 | {contextHolder}
99 |
100 | );
101 | };
102 |
103 | export default Item;
104 |
--------------------------------------------------------------------------------
/frontend/src/components/branch/mergeDialog.tsx:
--------------------------------------------------------------------------------
1 | import {Button, Modal, Result} from 'antd';
2 | import { LoadingOutlined } from "@ant-design/icons"
3 | import {useDispatch, useSelector} from "react-redux";
4 | import {State} from "../../store";
5 | import {useState, useImperativeHandle, forwardRef} from "react";
6 | import {success, warning} from "../../utils/common";
7 | import { PreMergeResult, MergeCommit, MergeRebase, MergeSquash} from "../../../wailsjs/go/repository/Repository";
8 | import {GetBranchHash,Commits} from "../../../wailsjs/go/repository/Repository";
9 | import {repository} from "../../../wailsjs/go/models";
10 | import {warningNotification} from "../../utils/notification";
11 | import {setLog} from "../../store/sliceMain";
12 |
13 |
14 | const MergeDialog = (props: {}, ref: any) => {
15 | const dispatch = useDispatch();
16 | const currentBranchName = useSelector((state: State) => state.main.selectedRepositoryBranch);
17 | const [openMerge, setOpenMerge] = useState(false);
18 | const [loading, setLoading] = useState(false);
19 |
20 | const [preMergeResult, setPreMergeResult] = useState(null);
21 |
22 | const [mergeContent, setMergeContent] = useState({
23 | ourBranchHash: "",
24 | ourBranchName: "",
25 | theirBranchHash: "",
26 | theirBranchName: "",
27 | });
28 |
29 | const OpenMergeDialog = async (mergeName: string) => {
30 | setOpenMerge(true)
31 | setLoading(true)
32 | try {
33 | let ourBranchHash = await GetBranchHash(currentBranchName)
34 | let theirBranchHash = await GetBranchHash(mergeName)
35 | setMergeContent({
36 | ourBranchHash,
37 | ourBranchName: currentBranchName,
38 | theirBranchHash,
39 | theirBranchName: mergeName,
40 | })
41 |
42 | const result = await PreMergeResult(ourBranchHash, theirBranchHash)
43 | setPreMergeResult(result)
44 |
45 | } catch (e) {
46 | console.log(e)
47 | warning(JSON.stringify(e))
48 | }finally {
49 | setTimeout(()=>{setLoading(false)},500)
50 | }
51 |
52 | }
53 |
54 |
55 | useImperativeHandle(ref, () => ({OpenMergeDialog}))
56 |
57 |
58 | const subTitle = () => {
59 | if(preMergeResult?.kind === 1){
60 | return There will be {preMergeResult.count} conflicted file when merging {mergeContent.theirBranchName} into {mergeContent.ourBranchName}
61 | }
62 | if(preMergeResult?.kind === 2){
63 | if(preMergeResult?.count === 0){
64 | return This branch is up to date with {mergeContent.theirBranchName}
65 | }else {
66 | return
67 | This will merge {preMergeResult?.count} commit{preMergeResult?.count >=2 ?'s':''} from {mergeContent.theirBranchName} into {mergeContent.ourBranchName}
68 | }
69 | }
70 | return
71 | }
72 |
73 |
74 | const rebaseMerge = () => {
75 | MergeRebase(mergeContent.ourBranchName,mergeContent.theirBranchName).then(out=>{
76 | success(out)
77 | return Commits(currentBranchName)
78 | }).then(l=>{
79 | dispatch(setLog(l))
80 | setOpenMerge(false)
81 | }).catch(e=>{
82 | warningNotification(e,{width:500})
83 | })
84 |
85 | }
86 | const squashMerge = () => {
87 | MergeSquash(mergeContent.ourBranchName,mergeContent.theirBranchName).then(out=>{
88 | success(out)
89 | return Commits(currentBranchName)
90 | }).then(l=>{
91 | dispatch(setLog(l))
92 | setOpenMerge(false)
93 | }).catch(e=>{
94 | warningNotification(e,{width:500})
95 | })
96 | }
97 | const commitMerge = () => {
98 | MergeCommit(mergeContent.ourBranchName,mergeContent.theirBranchName).then(out=>{
99 | success(out)
100 | return Commits(currentBranchName)
101 | }).then(l=>{
102 | dispatch(setLog(l))
103 | setOpenMerge(false)
104 | }).catch(e=>{
105 | warningNotification(e,{width:500})
106 | })
107 | }
108 |
109 | return {
114 | setOpenMerge(false)
115 | setPreMergeResult(null)
116 | }}
117 | footer={[
118 |
119 | Rebase
120 | ,
121 |
122 | Squash and merge
123 | ,
124 |
125 | Create a merge commit
126 | ,
127 | ]}
128 | >
129 | :''}
132 | subTitle={subTitle()}/>
133 |
134 | }
135 |
136 | export default forwardRef(MergeDialog)
137 |
138 |
139 |
--------------------------------------------------------------------------------
/frontend/src/components/branch/style.scss:
--------------------------------------------------------------------------------
1 | .branch-content {
2 | p {
3 | margin-bottom: 10px;
4 | color: #6a6a6a;
5 | }
6 |
7 | .item {
8 | display: flex;
9 | justify-content: space-between;
10 | align-items: center;
11 | color: #6a6a6a;
12 | margin: 10px 0;
13 | }
14 | .title{
15 |
16 | }
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/frontend/src/components/changes/FileState.tsx:
--------------------------------------------------------------------------------
1 | import {repository} from "../../../wailsjs/go/models"
2 | import React from "react";
3 | import {Checkbox, Dropdown, MenuProps,Modal} from 'antd';
4 | import { WarningOutlined } from "@ant-design/icons"
5 | import {diffWorkTreeStageAsync} from "../../store/sliceWorkDiff"
6 | import {useDispatch, useSelector} from "react-redux";
7 | import {State} from "../../store";
8 | import { DiscardChanges } from "../../../wailsjs/go/repository/Repository"
9 | import {success, warning} from "../../utils/common"
10 | import {updateWorkZone} from "../../utils/repo";
11 |
12 | const color = (s: string) => {
13 | // Unmodified StatusCode = ' '
14 | // Untracked StatusCode = '?'
15 | // Modified StatusCode = 'M'
16 | // Added StatusCode = 'A'
17 | // Deleted StatusCode = 'D'
18 | // Renamed StatusCode = 'R'
19 | // Copied StatusCode = 'C'
20 | // UpdatedButUnmerged StatusCode = 'U'
21 | switch (s) {
22 | case "M":
23 | return {background: "#d48806"}
24 | case "A":
25 | return {background: "#7cb305"}
26 | case "D":
27 | return {background: "#d4380d"}
28 | case "R":
29 | return {background: "#0958d9"}
30 | case "C":
31 | return {background: "#531dab"}
32 | case "U":
33 | return {background: "#c41d7f"}
34 | default:
35 | return {background: "#8c8c8c"}
36 | }
37 |
38 | }
39 |
40 |
41 | const FileState = (props: { s: repository.FileStatus }) => {
42 | const path = useSelector((state: State) => state.diffWork.fileStatus?.path);
43 | const repoId = useSelector((state: State) => state.main.selectedRepositoryId);
44 | const branchName = useSelector((state: State) => state.main.selectedRepositoryBranch);
45 | const dispatch = useDispatch();
46 |
47 | const contextMenu = (path:string):MenuProps['items'] => {
48 |
49 | const confirmDiscardChanges = () => {
50 | Modal.confirm({
51 | title: 'Confirm discard changes.',
52 | icon: ,
53 | content:
54 |
This file will be restored to its previous state.
55 |
file:{path}
56 |
,
57 | onOk() {
58 | return new Promise((resolve, reject) => {
59 | DiscardChanges(path).then(()=>{
60 | resolve("success")
61 | return updateWorkZone(repoId,branchName)
62 | }).catch((e)=>{
63 | reject(e)
64 | })
65 | }).catch((e) => warning(e));
66 | },
67 | onCancel() {},
68 | });
69 | };
70 |
71 | const discardChanges = {
72 | label:DiscardChanges ,
73 | key:'discard-changes',
74 | icon:
75 | }
76 | return [discardChanges]
77 | }
78 |
79 | return
80 |
81 |
82 | {dispatch(diffWorkTreeStageAsync(props.s))}}
86 | >{props.s.name}
87 |
88 |
89 | {props.s.staging}
90 | {props.s.worktree}
91 |
92 |
93 |
94 | }
95 |
96 | export default FileState
97 |
--------------------------------------------------------------------------------
/frontend/src/components/changes/Index.tsx:
--------------------------------------------------------------------------------
1 | import "./style.scss"
2 | import {State} from "../../store";
3 | import Block from "../block/Index"
4 | import FileState from "./FileState"
5 | import React, {useState,useMemo,useEffect} from 'react';
6 | import {useDispatch, useSelector} from "react-redux";
7 | import { Space, Checkbox,Badge} from 'antd';
8 | import { PresetStatusColorType } from "antd/es/_util/colors"
9 | import {FileStatus} from "../../../wailsjs/go/repository/Repository"
10 | import type {CheckboxChangeEvent} from 'antd/es/checkbox';
11 | import type {CheckboxValueType} from 'antd/es/checkbox/Group';
12 | import {success, warning} from "../../utils/common";
13 | import {setRepositoryStatus, updateWorkZone} from "../../utils/repo";
14 | import {setStatus} from "../../store/sliceMain";
15 | import SubmitView from "./SubmitView";
16 |
17 |
18 |
19 | const Changes = () => {
20 | const dispatch = useDispatch();
21 | const branch = useSelector((state: State) => state.main.selectedRepositoryBranch);
22 | const selectedRepositoryId = useSelector((state: State) => state.main.selectedRepositoryId);
23 | const [badgeStatus,setBadgeStatus] = useState("success")
24 |
25 | const fileState = useSelector((state: State) => state.main.currentlyRepositoryFileState);
26 |
27 | const changedFileInWorkspace = useMemo( ()=>fileState.filter(r=>(r.worktree != " ")).map(r => r.path),[fileState])
28 | const unchangedFileInWorkspace = useMemo( ()=>fileState.filter(r=>(r.worktree == " ")).map(r => r.path),[fileState])
29 |
30 | const [checkedList, setCheckedList] = useState(unchangedFileInWorkspace);
31 | const [indeterminate, setIndeterminate] = useState(true);
32 | const [checkAll, setCheckAll] = useState(false);
33 |
34 | useEffect(()=>{
35 | setCheckedList(unchangedFileInWorkspace)
36 | },[fileState])
37 |
38 | useEffect(()=>{
39 | if(fileState.length===0){
40 | setCheckAll(false);
41 | setIndeterminate(false);
42 | return
43 | }
44 | setCheckAll(checkedList.length === fileState.length);
45 | setIndeterminate(checkedList.length !==0 && checkedList.length < fileState.length);
46 | },[checkedList])
47 |
48 | const commitSuccess = async () => {
49 | try {
50 | setCheckedList([])
51 | await updateWorkZone(selectedRepositoryId,branch)
52 | }catch (e) {
53 | console.log(e)
54 | warning(JSON.stringify(e))
55 | }
56 | }
57 |
58 | const onChange = (list: CheckboxValueType[]) => {
59 | setCheckedList(list as string[]);
60 | };
61 |
62 | const onCheckAllChange = (e: CheckboxChangeEvent) => {
63 | let checkedList = e.target.checked ? [...changedFileInWorkspace,...unchangedFileInWorkspace] : unchangedFileInWorkspace
64 | setCheckedList(checkedList);
65 | };
66 |
67 | const getChangesStatus = () => {
68 | setBadgeStatus('processing')
69 | FileStatus().then((s)=>{
70 | dispatch(setStatus(s))
71 | setRepositoryStatus(selectedRepositoryId,Array.isArray(s)&&s.length !==0)
72 | }).catch(e=>{
73 | console.log(e)
74 | warning( JSON.stringify(e))
75 | }).finally(()=>{
76 | setTimeout(()=>{
77 | setBadgeStatus('success')
78 | },1000)
79 | })
80 | }
81 |
82 | const action =
83 | {checkedList.length}
84 |
85 | Check all
86 |
87 |
88 |
89 |
90 | return (
91 |
94 |
95 |
96 | }
97 | bottom={}
98 | action={action}>
99 |
103 | {fileState.map(s => )}
104 |
105 |
106 | );
107 | };
108 |
109 | export default Changes;
110 |
--------------------------------------------------------------------------------
/frontend/src/components/changes/SubmitView.tsx:
--------------------------------------------------------------------------------
1 | import {State} from "../../store";
2 | import React, {useState} from "react";
3 | import {Button, Input,Space} from 'antd';
4 | import { useSelector} from "react-redux";
5 | import {success, warning} from "../../utils/common"
6 | import {Commit} from "../../../wailsjs/go/repository/Repository"
7 |
8 | const {TextArea} = Input;
9 |
10 | const SubmitView = (props: { checkedList :string[],unchangedFileInWorkspace:string[],success:()=>void }) => {
11 | const [commitName,setCommitName] = useState("")
12 | const [commitMessage,setCommitMessage] = useState("")
13 | const branch = useSelector((state: State) => state.main.selectedRepositoryBranch);
14 |
15 | const commit = async () => {
16 | if(commitName.length === 0){
17 | warning("Please fill in the title for the submission.")
18 | return
19 | }
20 | if(props.checkedList.length === 0 && props.unchangedFileInWorkspace.length==0){
21 | warning("Please check the box for the file you want to submit.")
22 | return
23 | }
24 | let submitList = props.checkedList.filter((item) => !props.unchangedFileInWorkspace.includes(item));
25 |
26 | try {
27 | const hash = await Commit(commitName,commitMessage,submitList);
28 | success("Commit hash:"+hash)
29 | setCommitName("")
30 | setCommitMessage("")
31 | props.success()
32 | }catch (e) {
33 | console.log(e)
34 | warning(JSON.stringify(e))
35 | }
36 | }
37 |
38 | return
39 |
40 | ) => {
41 | setCommitName(e.target.value)
42 | }} placeholder="Title"/>
43 |
48 |
49 | }
50 |
51 | export default SubmitView
52 |
--------------------------------------------------------------------------------
/frontend/src/components/changes/style.scss:
--------------------------------------------------------------------------------
1 | .status-file-list {
2 | padding: 12px;
3 | display: flex;
4 | flex-direction: column;
5 |
6 | .file {
7 | display: flex;
8 | justify-content: space-between;
9 | align-items: center;
10 | width: 100%;
11 | margin: 6px 0;
12 |
13 | label>span {
14 | &:last-child {
15 | width: 100%;
16 | }
17 | }
18 | .file-name {
19 | width: 100%;
20 | display: block;
21 | flex: 1;
22 | -webkit-user-select: none;
23 | -moz-user-select: none;
24 | -ms-user-select: none;
25 | user-select: none;
26 | overflow: hidden;
27 | max-width: 220px;
28 | text-overflow: ellipsis;
29 | white-space: nowrap;
30 | //opacity: 0.45;
31 | font-size: 13px;
32 | cursor: pointer;
33 | }
34 |
35 |
36 | &:hover .file-name {
37 | opacity: 1;
38 | }
39 |
40 | .dot {
41 | display: flex;
42 | justify-content: center;
43 | align-items: center;
44 | font-size: 12px;
45 | border-radius: 50%;
46 | color: #fff;
47 | width: 18px;
48 | height: 18px;
49 | margin: 0 2px;
50 | background: #eee;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/frontend/src/components/dialog/About.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Modal} from "antd";
3 | import {useDispatch, useSelector} from "react-redux";
4 | import {State} from "../../store";
5 | import { version } from "../../config/app"
6 | import {setOpenAbout} from "../../store/sliceSetting";
7 |
8 |
9 |
10 | const About = () => {
11 | const showAbout = useSelector((state: State) => state.setting.showAbout);
12 | const dispatch = useDispatch();
13 |
14 | return (<>
15 | {dispatch(setOpenAbout(false))}}
20 | footer={[]}
21 | >
22 |
23 |
Version:{version}
24 |
github:https://github.com/xusenlin/git-helper
25 |
26 |
27 | >)
28 | }
29 |
30 | export default About
31 |
--------------------------------------------------------------------------------
/frontend/src/components/dialog/Input.tsx:
--------------------------------------------------------------------------------
1 | import React, {ReactNode, useState} from "react"
2 | import {Button, Input, Modal} from "antd";
3 |
4 | type Props = {
5 | title:string
6 | msg?:string
7 | action:ReactNode
8 | defaultVal?:string
9 | inputVal:(v:string)=>void
10 | }
11 |
12 | const DialogInput:React.FC = (props:Props) => {
13 | const [val,setVal] = useState(props.defaultVal?props.defaultVal:"")
14 | const [open,setOpen] = useState(false)
15 |
16 | return (<>
17 |
18 | { setOpen(true)}}>{props.action}
19 | {setOpen(false)}}
24 | footer={[
25 | {
26 | props.inputVal(val)
27 | setOpen(false)
28 | }}>OK
29 | ]}
30 | width='380px'
31 | >
32 |
33 |
) => {
36 | setVal(e.target.value)
37 | }}
38 | />
39 | {props.msg &&
{props.msg}
}
40 |
41 |
42 | >)
43 | }
44 |
45 | export default DialogInput
46 |
--------------------------------------------------------------------------------
/frontend/src/components/dialog/Theme.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Modal,Space} from "antd";
3 | import {State} from "../../store";
4 | import {useDispatch, useSelector} from "react-redux";
5 | import { theme,localThemeKey } from "../../config/app"
6 | import {setOpenThemeSetting,setThemeColor} from "../../store/sliceSetting";
7 |
8 |
9 | const ColorsBlock = (p:{c:string})=>{
10 | const themeColor = useSelector((state: State) => state.setting.themeColor);
11 | const dispatch = useDispatch();
12 |
13 | return {
15 | dispatch(setThemeColor(p.c))
16 | localStorage.setItem(localThemeKey,p.c)
17 | }}
18 | style={{
19 | width:40,
20 | height:40,
21 | cursor:"pointer",
22 | borderRadius:"50%",
23 | background:p.c,
24 | display:"flex",
25 | justifyContent:"center",
26 | alignItems:"center"
27 | }}
28 | >
29 | {p.c === themeColor &&
}
30 |
31 | }
32 |
33 |
34 | const ThemeSetting = () => {
35 | const showThemeSetting = useSelector((state: State) => state.setting.showThemeSetting);
36 |
37 | const dispatch = useDispatch();
38 |
39 |
40 | return (<>
41 | {dispatch(setOpenThemeSetting(false))}}
46 | footer={[]}
47 | >
48 |
49 | {theme.map(r=>)}
50 |
51 |
52 | >)
53 | }
54 |
55 | export default ThemeSetting
56 |
--------------------------------------------------------------------------------
/frontend/src/components/diff/DiffCommitView.tsx:
--------------------------------------------------------------------------------
1 | import './style.scss'
2 | import {Badge} from "antd"
3 | import {useDispatch, useSelector} from "react-redux";
4 | import {State} from "../../store";
5 | import {repository} from "../../../wailsjs/go/models"
6 | import FailedInfo from "./FailedInfo"
7 | import {useEffect} from "react";
8 | import {resetState} from "../../store/sliceCommitDiff";
9 |
10 | const buildFlag = (d:string) => {
11 | let str = d.split("")
12 | return str.map((r,index)=>{
13 | let c = ""
14 | if(r==="+"){
15 | c = "#4caf50"
16 | }else if(r==="-"){
17 | c = "#f44336"
18 | }else {
19 | c = "#222"
20 | }
21 | return {r}
22 | })
23 | }
24 |
25 | const diffRow = (c: repository.ChangesFile) => {
26 |
27 | const color = (d:string):string => {
28 | if(/Bin 0 ->/.test(d)){
29 | return "#4caf50"
30 | }
31 | if(/Bin \d* -> 0 /.test(d)){
32 | return "#f44336"
33 | }
34 | return "#222"
35 | }
36 |
37 | return
38 |
{c.fileName}
39 | {/^\d* [+-]*$/.test(c.desc)?
{buildFlag(c.desc)}
:
{c.desc}
}
40 |
41 |
42 | }
43 |
44 |
45 | const DiffCommitView = () => {
46 | const diff = useSelector((state: State) => state.diffCommit);
47 | const logs = useSelector((state: State) => state.main.currentlyRepositoryCommits);
48 |
49 | const dispatch = useDispatch();
50 |
51 | useEffect(()=>{
52 | console.log("DiffCommitViewEffect")
53 | dispatch(resetState())
54 | },[logs])
55 |
56 | return
57 |
58 |
59 |
{ diff.statistics }
60 |
61 |
62 | { diff.failedInfo ? FailedInfo(diff.failedInfo) : diff.filesInfo.map(r=>diffRow(r))}
63 |
64 |
65 | }
66 | export default DiffCommitView
67 |
--------------------------------------------------------------------------------
/frontend/src/components/diff/DiffWorkView.tsx:
--------------------------------------------------------------------------------
1 | import './style.scss'
2 | import { useEffect } from "react"
3 | import {useDispatch, useSelector} from "react-redux";
4 | import {State} from "../../store";
5 | import {repository} from "../../../wailsjs/go/models"
6 | import {resetState} from "../../store/sliceWorkDiff";
7 | import FailedInfo from "./FailedInfo"
8 |
9 | const diffRow = (c: repository.DiffContent) => {
10 | switch (c.type) {
11 | case 1:
12 | return {c.content}
13 | case 2:
14 | return {c.content}
15 | default:
16 | return {c.content}
17 | }
18 | }
19 |
20 |
21 |
22 | const DiffWorkView = () => {
23 | const dispatch = useDispatch();
24 | const diff = useSelector((state: State) => state.diffWork);
25 | const changesFileFile = useSelector((state: State) => state.main.currentlyRepositoryFileState);
26 | useEffect(()=>{
27 | console.log("DiffWorkViewUseEffect")
28 | dispatch(resetState())
29 | },[changesFileFile])
30 |
31 | return
32 | { diff.failedInfo ? FailedInfo(diff.failedInfo) : diff.diffText.map(r => diffRow(r))}
33 |
34 | }
35 | export default DiffWorkView
36 |
--------------------------------------------------------------------------------
/frontend/src/components/diff/FailedInfo.tsx:
--------------------------------------------------------------------------------
1 | import {Result} from "antd";
2 |
3 | const Info = (tip:string) => {
4 | return
5 |
6 |
7 | }
8 | export default Info
9 |
--------------------------------------------------------------------------------
/frontend/src/components/diff/style.scss:
--------------------------------------------------------------------------------
1 | .diff-tip{
2 | height: 100%;
3 | display: flex;
4 | justify-content: center;
5 | align-items: center;
6 | }
7 | .diff{
8 | height: 100%;
9 | p{
10 | margin:0;
11 | padding:6px;
12 | white-space: pre-wrap;
13 | font-size:14px;
14 | }
15 | }
16 | .diff-commit{
17 | display: flex;
18 | height: calc(100vh - 60px);
19 | flex-direction: column;
20 | .diff-commit-row{
21 | padding:8px;
22 | font-size: 14px;
23 | display: flex;
24 | align-items: center;
25 | .diff-commit-left{
26 | color: #222;
27 |
28 | }
29 | .diff-commit-right{
30 | margin-left: 10px;
31 | }
32 | .diff-commit-left,.diff-commit-right{
33 | width: 50%;
34 | white-space: nowrap;
35 | overflow: hidden;
36 | text-overflow: ellipsis;
37 | }
38 | }
39 | .diff-changes-file{
40 | flex: 1;
41 | overflow-y: auto;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/frontend/src/components/history/Index.tsx:
--------------------------------------------------------------------------------
1 | import Block from "../block/Index"
2 | import Item from "./Item"
3 | import {useSelector} from "react-redux";
4 | import {State} from "../../store";
5 |
6 | const History = () => {
7 | const logs = useSelector((state: State) => state.main.currentlyRepositoryCommits);
8 |
9 | return (
10 |
11 |
12 | {logs.map((r,index)=> )}
13 |
14 |
15 | );
16 | };
17 |
18 | export default History;
19 |
--------------------------------------------------------------------------------
/frontend/src/components/history/Item.tsx:
--------------------------------------------------------------------------------
1 | import {State} from "../../store";
2 | import {Badge, Card, Space} from "antd"
3 | import {useDispatch, useSelector} from "react-redux";
4 | import {hashLength} from "../../config/app";
5 | import {clipboard} from "../../utils/common";
6 | import {repository} from "../../../wailsjs/go/models";
7 | import {diffCommitAsync} from "../../store/sliceCommitDiff";
8 | import {SnippetsOutlined, FieldTimeOutlined, UserOutlined, EyeOutlined} from "@ant-design/icons";
9 |
10 |
11 | const style = {
12 | item: {
13 | display: "flex",
14 | justifyContent: "space-between",
15 | alignItems: "center",
16 | color: "#6a6a6a",
17 | },
18 | msg: {color: "#6a6a6a", fontSize: 13, marginTop: 10}
19 | }
20 |
21 | const Item = (props: { l: repository.Commit, nextHash: string }) => {
22 | const themeColor = useSelector((state: State) => state.setting.themeColor);
23 | const commitId = useSelector((state: State) => state.diffCommit.commitId);
24 | const dispatch = useDispatch()
25 |
26 | const extra =
27 | {
29 | await clipboard(props.l.hash)
30 | }}
31 | style={{cursor: "pointer", opacity: 0.45}}/>
32 | {props.nextHash &&
33 | {
35 | let commitId= props.l.hash
36 | let commitId2 = props.nextHash
37 | dispatch(diffCommitAsync({commitId,commitId2}))
38 | }}
39 | style={{cursor: "pointer", opacity: 0.45}}/>
40 | }
41 |
42 |
43 | return (
44 | }
49 | extra={extra}
50 | style={{marginBottom: 10}}
51 | >
52 |
53 |
54 |
55 | {props.l.committer.name}
56 |
57 |
58 |
59 | {props.l.committer.when}
60 |
61 |
62 | {props.l.message}
63 |
64 | );
65 | };
66 |
67 | export default Item;
68 |
--------------------------------------------------------------------------------
/frontend/src/components/repoSetting/Action.tsx:
--------------------------------------------------------------------------------
1 | import {Space} from 'antd';
2 | import {useDispatch} from "react-redux";
3 | import {delCategory} from "../../store/sliceCategory";
4 | import { DeleteOutlined } from '@ant-design/icons';
5 | function Action(props:{name:string}) {
6 |
7 | const dispatch = useDispatch()
8 |
9 | return (
10 |
11 | {/* */}
12 | {dispatch(delCategory(props.name))}} style={{cursor: "pointer", opacity: 0.45}} />
13 |
14 | )
15 | }
16 |
17 | export default Action
18 |
--------------------------------------------------------------------------------
/frontend/src/components/repoSetting/Card.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {State} from "../../store";
3 | import Action from "./Action"
4 | import DialogInput from "../dialog/Input";
5 | import {Card, Space, Popover, Modal} from 'antd';
6 | import {useDispatch, useSelector} from "react-redux";
7 | import {Droppable, Draggable} from 'react-beautiful-dnd'
8 | import { GitRemoteUrl } from "../../../wailsjs/go/repository/Repository"
9 | import {DeleteOutlined, InfoCircleOutlined, EditOutlined, CopyOutlined} from "@ant-design/icons"
10 | import {Category, Repository, delRepository, editRepositoryName} from "../../store/sliceCategory";
11 | import {clipboard, warning} from "../../utils/common";
12 |
13 | const Details = (p: {r:Repository}) =>
14 |
ID:{p.r.id}
15 |
Name:{p.r.name}
16 |
Path:{p.r.path}
17 |
18 |
19 |
20 | const Block = (props: Category) => {
21 | const dispatch = useDispatch();
22 | const selectedId = useSelector((state: State) => state.main.selectedRepositoryId);
23 |
24 | const copyRemoteUrl = (path:string)=>{
25 | GitRemoteUrl(path).then(r=>{
26 | return clipboard(r,"RemoteUrl")
27 | }).catch(e=>{
28 | warning(e)
29 | })
30 | }
31 |
32 | const delRepo = (id:string)=>{
33 | Modal.warning({
34 | closable:true,
35 | title: 'Confirm message',
36 | content: 'Confirm removal of this git repository directory? You can add it back again later.',
37 | onOk(){
38 | dispatch(delRepository(id))
39 | }
40 | });
41 | }
42 |
43 | return (
44 | } className='card'>
45 |
46 | {(provided, snapshot) => (
47 |
53 | {props.repositories.map((r, i)=>(
54 |
55 | {(provided, snapshot) => (
56 |
62 |
63 | { r.name }
64 |
65 |
66 | {copyRemoteUrl(r.path)}} style={{cursor: "pointer", opacity: 0.45}} />
67 | } title="Repository details" trigger="hover">
68 |
69 |
70 | } title="Edit the repository name" inputVal={v=>{dispatch(editRepositoryName({id:r.id,name:v}))}}/>
71 | { (selectedId !== r.id) && {delRepo(r.id)}} style={{cursor: "pointer", opacity: 0.45}} /> }
72 |
73 |
74 | )}
75 |
76 | ))}
77 | {provided.placeholder}
78 |
79 | )}
80 |
81 |
82 | );
83 | };
84 |
85 | export default Block;
86 |
--------------------------------------------------------------------------------
/frontend/src/components/repoSetting/Index.tsx:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 | import Card from "./Card"
3 | import React from 'react';
4 | import {Modal } from 'antd';
5 | import {State} from "../../store/index";
6 | import {DragDropContext} from 'react-beautiful-dnd'
7 | import {useSelector, useDispatch} from "react-redux";
8 | import {moveRepository} from "../../store/sliceCategory";
9 | import {setOpenRepositorySetting} from "../../store/sliceSetting";
10 |
11 |
12 | const RepoSetting = () => {
13 | const categories = useSelector((state: State) => state.categories.val);
14 | const showRepositorySetting = useSelector((state: State) => state.setting.showRepositorySetting);
15 |
16 | const dispatch = useDispatch()
17 | return (
18 |
19 | {dispatch(setOpenRepositorySetting(false))}}
24 | width='80%'
25 | footer={[]}
26 | >
27 |
28 | {
30 | let {source, destination} = e
31 | destination && dispatch(moveRepository({source, destination}))
32 | }}
33 | >
34 | {categories.map(r => )}
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | export default RepoSetting;
42 |
--------------------------------------------------------------------------------
/frontend/src/components/repoSetting/index.scss:
--------------------------------------------------------------------------------
1 | .repo-setting{
2 | padding:30px;
3 | display:flex;
4 | flex-wrap:wrap;
5 | justify-content:flex-start;
6 | .card{
7 | width: 32%;
8 | overflow: hidden;
9 | margin:0.6666%;
10 | .card-content{
11 | width: 100%;
12 | padding: 6px;
13 | height: 200px;
14 | overflow-x: hidden;
15 | overflow-y: auto;
16 | .node{
17 | width: 100%;
18 | white-space: nowrap;
19 | height: 32px;
20 | text-overflow: ellipsis;
21 | overflow: hidden;
22 | display: flex;
23 | justify-content: space-between;
24 | background: #fff;
25 | align-items: center;
26 | padding: 0 6px;
27 | border-radius: 6px;
28 | .action{
29 | padding: 0 5px;
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/frontend/src/components/sides/Sides.tsx:
--------------------------------------------------------------------------------
1 | import {Badge, Menu} from 'antd';
2 | import React from 'react';
3 | import type {MenuProps} from 'antd';
4 | import {State} from '../../store/index'
5 | import {warning} from "../../utils/common";
6 | import {useDispatch, useSelector} from 'react-redux';
7 | import {getRepositoryPathById, updateWorkZone} from "../../utils/repo";
8 | import {setRepository, setAllBranch, resetState,setBranch} from "../../store/sliceMain"
9 | import {GetLocalBranch,SwitchRepository,GetCurrentBranch} from "../../../wailsjs/go/repository/Repository";
10 |
11 |
12 |
13 | type MenuItem = Required['items'][number];
14 |
15 | const getItem = (
16 | label: React.ReactNode,
17 | key: React.Key,
18 | icon?: React.ReactNode,
19 | children?: MenuItem[],
20 | type?: 'group',
21 | ): MenuItem => {
22 | return {
23 | key,
24 | icon,
25 | children,
26 | label,
27 | type,
28 | } as MenuItem;
29 | }
30 |
31 |
32 | const Sides = () => {
33 | const dispatch = useDispatch();
34 | const repositoryId = useSelector((state: State) => state.main.selectedRepositoryId);
35 | const categories = useSelector((state: State) => state.categories.val);
36 |
37 |
38 | const menuData: MenuItem[] = categories.map(c => {
39 | let children = c.repositories.map(r => getItem(, r.id))
40 | return getItem(c.name, c.name, null, children, 'group')
41 | })
42 | const onClick: MenuProps['onClick'] = async (e) => {
43 | const {key} = e
44 | if(key === repositoryId){
45 | return
46 | }
47 | try {
48 | const path = getRepositoryPathById(key,categories)
49 | if(!path){
50 | warning("No git repository path was found for the specified ID.")
51 | return
52 | }
53 | await SwitchRepository(path)
54 |
55 | dispatch(resetState())
56 |
57 | dispatch(setRepository(key))
58 |
59 | const b = await GetLocalBranch()
60 | dispatch(setAllBranch(b))
61 |
62 | const branch = await GetCurrentBranch()
63 | dispatch(setBranch(branch))
64 | await updateWorkZone(key,branch)
65 |
66 | } catch (e) {
67 | warning(JSON.stringify(e))
68 | }
69 |
70 | };
71 | return (
72 |
73 |
81 |
82 | )
83 | }
84 |
85 | export default Sides
86 |
--------------------------------------------------------------------------------
/frontend/src/components/sides/SidesTop.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Space} from 'antd';
3 | import {useDispatch} from "react-redux";
4 | import DialogInput from "../dialog/Input"
5 | import RepoSetting from '../repoSetting/Index'
6 | import {addCategory} from "../../store/sliceCategory";
7 | import {SettingOutlined,PlusOutlined} from '@ant-design/icons';
8 | import {setOpenRepositorySetting, setOpenThemeSetting} from "../../store/sliceSetting";
9 |
10 | const SidesTop: React.FC = () => {
11 |
12 | const dispatch = useDispatch()
13 | return (
14 | <>
15 |
16 | {dispatch(setOpenThemeSetting(true))}}>
17 |
19 |
22 |
25 |
28 |
31 |
34 |
35 |
36 | dispatch(setOpenRepositorySetting(true))} style={{cursor: "pointer", opacity: 0.45}}/>
37 | } title="Add Category" inputVal={v=>{dispatch(addCategory(v))}}/>
38 |
39 |
40 | >
41 | )
42 | }
43 |
44 | export default SidesTop
45 |
46 |
--------------------------------------------------------------------------------
/frontend/src/components/tag/Index.tsx:
--------------------------------------------------------------------------------
1 | import "./style.scss"
2 | import Item from "./Item"
3 | import {State} from "../../store";
4 | import {useDispatch, useSelector} from "react-redux";
5 | import {Button, Input, Space,Drawer,Empty} from "antd";
6 | import {success, warning} from "../../utils/common";
7 | import {repository} from "../../../wailsjs/go/models";
8 | import React, {useState,useMemo,useEffect} from "react";
9 | import {setOpenRepositoryTag} from "../../store/sliceSetting";
10 | import { Tags,RemoteTags } from "../../../wailsjs/go/repository/Repository"
11 | import { CreateTag } from "../../../wailsjs/go/repository/Repository"
12 |
13 | const { TextArea } = Input;
14 | const Tag = () => {
15 |
16 | const limit = 20
17 |
18 | const dispatch = useDispatch();
19 | const selectedRepositoryId = useSelector((state: State) => state.main.selectedRepositoryId);
20 | const showRepositoryTag = useSelector((state: State) => state.setting.showRepositoryTag);
21 | const [tags,setTags] = useState([])
22 | const [remoteTags,setRemoteTags] = useState([])
23 |
24 | const [tagName,setTagName] = useState("")
25 | const [tagMessage,setTagMessage] = useState("")
26 | const [keyword,setKeyword] = useState("")
27 |
28 | const computedTags = useMemo(() => (tags.filter(r=>r.name.indexOf(keyword)!==-1)), [keyword,tags]);
29 |
30 | const getTag = () => {
31 | Tags().then(t=>{
32 | setTags(t||[])
33 | }).catch(e=>{
34 | console.log(e)
35 | warning("Tag:" + JSON.stringify(e))
36 | })
37 |
38 | RemoteTags().then(t=>{
39 | setRemoteTags(t||[])
40 | }).catch(e=>{
41 | warning("RemoteTags:" + JSON.stringify(e))
42 | })
43 | }
44 |
45 | useEffect(() => {
46 | if (!selectedRepositoryId){
47 | return
48 | }
49 | getTag()
50 |
51 | }, [selectedRepositoryId]);
52 |
53 | const addTag = () => {
54 | CreateTag(tagName,tagMessage).then(out=>{
55 | getTag()
56 | setTagName("")
57 | setTagMessage("")
58 | success(out)
59 | }).catch(e=>{
60 | warning("CreateTag" + JSON.stringify(e))
61 | })
62 | }
63 | const onCloseTag = () => {
64 | dispatch(setOpenRepositoryTag(false))
65 | }
66 |
67 | const bottom =
68 |
69 | ) => {
70 | setTagName(e.target.value)
71 | }}/>
72 |
77 |
78 |
79 | const content = <>
80 |
81 | ) => {
82 | setKeyword(e.target.value)
83 | }} />
84 | { (computedTags.length > limit ? computedTags.slice(0,limit) : computedTags).map(r=> )}
85 |
86 |
87 | {bottom}
88 |
89 | >
90 | return (
91 | Total:{tags.length}}
94 | bodyStyle={{display:'flex',flexDirection:"column",padding:0}}
95 | placement="right"
96 | onClose={onCloseTag}
97 | destroyOnClose={true}
98 | open={showRepositoryTag}
99 | >
100 | {selectedRepositoryId?content: }
101 |
102 | );
103 | };
104 |
105 | export default Tag;
106 |
--------------------------------------------------------------------------------
/frontend/src/components/tag/Item.tsx:
--------------------------------------------------------------------------------
1 | import {Badge, Card, Checkbox, Modal,Space,Spin,message} from "antd"
2 | import {repository} from "../../../wailsjs/go/models"
3 | import {DelTag,PushTag} from "../../../wailsjs/go/repository/Repository"
4 | import {warning, success, clipboard} from "../../utils/common";
5 | import {DeleteOutlined,ArrowUpOutlined, FieldTimeOutlined, SnippetsOutlined, TagOutlined} from "@ant-design/icons"
6 |
7 |
8 | const Item = (props: {isRemoteSync:boolean, t: repository.Tag, refresh: () => void }) => {
9 | const [messageApi, contextHolder] = message.useMessage();
10 |
11 | const pushTag = (name:string) => {
12 | messageApi.open({
13 | type: 'loading',
14 | content: 'Pushing...',
15 | duration: 0,
16 | });
17 | PushTag(name).then(out=>{
18 | success(out)
19 | props.refresh()
20 | }).catch(e=>{
21 | warning(e)
22 | }).finally(messageApi.destroy)
23 | }
24 |
25 | const delTag = (name: string) => {
26 | Modal.warning({
27 | closable: true,
28 | title: 'Confirm message',
29 | content:
30 |
Are you sure you want to delete this tag??
31 |
Delete the remote Tag at the same time.
32 |
,
33 | onOk() {
34 | const checkbox = document.getElementById("delRemoteTagCheckbox") as HTMLInputElement
35 | DelTag(name, checkbox.checked).then(() => {
36 | success("Delete success");
37 | props.refresh()
38 | }).catch(e => {
39 | warning(JSON.stringify(e))
40 | })
41 | }
42 | });
43 | }
44 | return (
45 | }
48 | extra={
49 |
50 | { !props.isRemoteSync && {
51 | pushTag(props.t.name)
52 | }} style={{cursor: "pointer", opacity: 0.45}}/> }
53 | {
54 | delTag(props.t.name)
55 | }} style={{cursor: "pointer", opacity: 0.45}}/>
56 |
57 | }
58 | style={{marginBottom: 10}}
59 | >
60 | {contextHolder}
61 |
62 |
{props.t.refName}
63 |
64 |
hash:{props.t.hash.substring(0, 7)}
65 |
{
66 | await clipboard(props.t.hash)
67 | }} style={{cursor: "pointer", opacity: 0.65}}/>
68 |
69 |
70 |
71 |
72 | {props.t.type}
73 |
74 |
75 |
76 | {props.t.time}
77 |
78 |
79 | {
80 | props.t.type !== "commit" &&
{props.t.message}
81 | }
82 |
83 |
84 | );
85 | };
86 |
87 | export default Item;
88 |
--------------------------------------------------------------------------------
/frontend/src/components/tag/style.scss:
--------------------------------------------------------------------------------
1 | .tag-content {
2 | p {
3 | margin-bottom: 10px;
4 | color: #6a6a6a;
5 | }
6 |
7 | .item {
8 | display: flex;
9 | justify-content: space-between;
10 | align-items: center;
11 | color: #6a6a6a;
12 | margin: 10px 0;
13 | }
14 |
15 |
16 | .msg {
17 | color: #6a6a6a;
18 | font-size: 13px;
19 | margin-top: 10px
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/components/topBar/TopBar.tsx:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 | import Nav from './breadcrumb/Nav'
3 | import Action from './action/Action'
4 |
5 | function TopBar() {
6 |
7 | return (
8 |
14 | )
15 | }
16 |
17 | export default TopBar
18 |
--------------------------------------------------------------------------------
/frontend/src/components/topBar/action/Action.tsx:
--------------------------------------------------------------------------------
1 | import {useDispatch, useSelector} from "react-redux";
2 | import {Button, Space} from 'antd';
3 | import {warning} from "../../../utils/common";
4 | import {warningNotification,successNotification} from "../../../utils/notification";
5 | import {OpenTerminal, OpenFileManage, GitPull, GitPush, Commits} from "../../../../wailsjs/go/repository/Repository"
6 |
7 | import {setOpenRepositoryTag, setOpenRepositoryBranch} from "../../../store/sliceSetting";
8 | import {
9 | ArrowUpOutlined,
10 | ArrowDownOutlined,
11 | CodeOutlined,
12 | FolderOpenOutlined,
13 | TagOutlined,
14 | BranchesOutlined,
15 | } from '@ant-design/icons';
16 | import React, {useState,useEffect} from "react";
17 | import {State, store} from "../../../store";
18 | import {setLog} from "../../../store/sliceMain";
19 |
20 | const Action = () => {
21 | const [pullLoading,setPullLoading] = useState(false)
22 | const [pushLoading,setPushLoading] = useState(false)
23 | const selectedRepositoryId = useSelector((state: State) => state.main.selectedRepositoryId);
24 | const branchName = useSelector((state: State) => state.main.selectedRepositoryBranch);
25 |
26 | const dispatch = useDispatch();
27 | useEffect(()=>{
28 | setPullLoading(false)
29 | setPushLoading(false)
30 | },[selectedRepositoryId])
31 | const openTerminal = async () => {
32 | if(!selectedRepositoryId){
33 | warning("please select a git repository first")
34 | return
35 | }
36 | try {
37 | await OpenTerminal()
38 | } catch (e) {
39 | warning(JSON.stringify(e))
40 | }
41 | }
42 | const openFileManage = () => {
43 | if(!selectedRepositoryId){
44 | warning("please select a git repository first")
45 | return
46 | }
47 | OpenFileManage().then(() => {
48 | }).catch(e => {
49 | warning(JSON.stringify(e))
50 | })
51 | }
52 | const openRepositoryTag = () => {
53 | dispatch(setOpenRepositoryTag(true))
54 | }
55 | const openRepositoryBranch = () => {
56 | dispatch(setOpenRepositoryBranch(true))
57 |
58 | }
59 | // const openMoreHelper = () => {
60 | // dispatch(setOpenMoreHelper(true))
61 | // }
62 | const pushRepo = () => {
63 | if(!selectedRepositoryId){
64 | warning("please select a git repository first")
65 | return
66 | }
67 | setPushLoading(true)
68 | GitPush().then(out => {
69 | successNotification(out,{width:500})
70 | return Commits(branchName)
71 | }).then(l=>{
72 | dispatch(setLog(l))
73 | }).catch(e => {
74 | warningNotification(e,{width:500})
75 | }).finally(()=>{setPushLoading(false)})
76 | }
77 | const pullRepo = () => {
78 | if(!selectedRepositoryId){
79 | warning("please select a git repository first")
80 | return
81 | }
82 | setPullLoading(true)
83 | GitPull().then(out => {
84 | successNotification(out,{width:500})
85 | return Commits(branchName)
86 | }).then(l=>{
87 | store.dispatch(setLog(l))
88 | }).catch(e => {
89 | warningNotification(e,{width:500})
90 | }).finally(()=>{setPullLoading(false)})
91 | }
92 | return (
93 |
94 | }/>
95 | }/>
96 | }/>
97 | }/>
98 | }/>
99 | }/>
100 | {/*}/>*/}
101 |
102 | )
103 | }
104 |
105 | export default Action
106 |
--------------------------------------------------------------------------------
/frontend/src/components/topBar/breadcrumb/Nav.tsx:
--------------------------------------------------------------------------------
1 | import {Breadcrumb} from 'antd';
2 | import {State} from "../../../store";
3 | import {setBranch} from "../../../store/sliceMain"
4 | import {getRepositoryById,updateWorkZone} from "../../../utils/repo"
5 | import {useSelector, useDispatch} from "react-redux";
6 | import {warning} from "../../../utils/common"
7 | import { SwitchBranch} from "../../../../wailsjs/go/repository/Repository"
8 | import {HomeOutlined, BranchesOutlined} from '@ant-design/icons';
9 |
10 | const Nav = () => {
11 | const main = useSelector((state: State) => state.main);
12 | const categories = useSelector((state: State) => state.categories.val);
13 | const dispatch = useDispatch();
14 |
15 | const selectBranch = async (b: string) => {
16 | if(b===main.selectedRepositoryBranch){
17 | return
18 | }
19 | try {
20 | const ok = await SwitchBranch(b)
21 | if(!ok){
22 | warning("Switch branch failed.")
23 | return
24 | }
25 | dispatch(setBranch(b))
26 |
27 | await updateWorkZone(main.selectedRepositoryId,b)
28 |
29 | }catch (e) {
30 | console.log(e)
31 | warning(JSON.stringify(e))
32 | }
33 |
34 | }
35 | const getCategoryNameById = (id: string): string | null => {
36 | let r = getRepositoryById(id,categories)
37 | if(r)return r.name
38 | return null
39 | }
40 |
41 | const branch = main.currentlyRepositoryLocalBranch.map(r => {
42 | return {
43 | key: r.name, label: selectBranch(r.name)}>{r.name}
44 | }
45 | })
46 |
47 | return (
48 |
49 |
50 |
51 | {getCategoryNameById(main.selectedRepositoryId) ||
52 | select repository }
53 |
54 | {main.selectedRepositoryId &&
55 |
56 |
57 | {main.selectedRepositoryBranch || select branch }
58 | }
59 |
60 | )
61 | }
62 |
63 | export default Nav
64 |
65 |
--------------------------------------------------------------------------------
/frontend/src/components/topBar/index.scss:
--------------------------------------------------------------------------------
1 | .top-bar{
2 | width: 100%;
3 | height: 60px;
4 | display: flex;
5 | align-items: center;
6 | padding: 0 20px;
7 | background-color: #f2f6f6;
8 | --wails-draggable:drag;
9 | .breadcrumb{
10 | --wails-draggable:no-drag;
11 | }
12 | .top-bar-content{
13 | /*--wails-draggable:no-drag;*/
14 | display: flex;
15 | width: 100%;
16 | align-items: center;
17 | justify-content: space-between;
18 | .action{
19 | margin: 0 10px;
20 | }
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/frontend/src/config/app.ts:
--------------------------------------------------------------------------------
1 | export const mainBranch = "main"
2 | export const hashLength = 7
3 | export const version = "0.0.1"
4 | export const localThemeKey = "GITHELPER:theme"
5 | export const theme = [
6 | "#673ab7","#f44336","#9c27b0",
7 | "#2196f3","#607d8b","#795548",
8 | "#4caf50","#ffc107","#009688"
9 | ]
10 |
--------------------------------------------------------------------------------
/frontend/src/config/gitCmd.ts:
--------------------------------------------------------------------------------
1 |
2 | export type GitCmd = {
3 | cmdName:string
4 | arg?:string[]
5 | desc?:string
6 | }
7 |
8 | export const remotePruneOrigin:GitCmd = {
9 | cmdName:"git",
10 | arg:['remote','prune','origin'],
11 | desc:"used to delete branches from the specified remote repository that no longer exist"
12 | }
13 |
--------------------------------------------------------------------------------
/frontend/src/main.tsx:
--------------------------------------------------------------------------------
1 | import './style.css'
2 | import App from './App'
3 | import React from 'react'
4 | import 'antd/dist/reset.css';
5 | import './nativeMenuEvents.ts'
6 | import {store} from './store';
7 | import {Provider} from 'react-redux';
8 | import {createRoot} from 'react-dom/client'
9 | import {ReadJsonFile} from "../wailsjs/go/main/App";
10 | import {Category, setRepository} from "./store/sliceCategory";
11 |
12 | const container = document.getElementById('root')
13 |
14 | const root = createRoot(container!)
15 |
16 | ReadJsonFile().then(r => {
17 | console.log("ReadJsonFile")
18 | const data = JSON.parse(r) as Category[]
19 | store.dispatch(setRepository(data))
20 | }).catch(e => {
21 | // warning(JSON.stringify(e))
22 | // console.log(e)
23 | })
24 |
25 | root.render(
26 |
27 |
28 |
29 | )
30 |
--------------------------------------------------------------------------------
/frontend/src/nativeMenuEvents.ts:
--------------------------------------------------------------------------------
1 | import {store} from './store';
2 | import {warning} from './utils/common';
3 | import {getRepositoryPathById} from './utils/repo';
4 | import {EventsOn} from "../wailsjs/runtime"
5 | import {IsGitRepository} from "../wailsjs/go/main/App"
6 | import {addDefaultRepository} from "./store/sliceCategory"
7 | import {setOpenRepositorySetting,setOpenRepositoryBranch,setOpenRepositoryTag,setOpenThemeSetting,setOpenAbout} from "./store/sliceSetting"
8 |
9 |
10 | //===================Repository Menu Event========
11 |
12 | EventsOn("Repository_A", async (id:string,path: string) => {
13 | let has = getRepositoryPathById(id,store.getState().categories.val)
14 | if(has){
15 | warning("This git repository already exists.");
16 | return
17 | }
18 | try {
19 | const is = await IsGitRepository(path)
20 | if (!is) {
21 | warning("This directory does not appear to be a Git repository.");
22 | return
23 | }
24 | const p = path.split('/')
25 | let name = p[p.length-1]
26 |
27 |
28 | store.dispatch(addDefaultRepository({id, path, name,status:"success"}))
29 | } catch (e) {
30 | console.log(e)
31 | warning(JSON.stringify(e))
32 | }
33 | })
34 | EventsOn("Repository_M",()=>{
35 | store.dispatch(setOpenRepositorySetting(true))
36 | })
37 |
38 | //===================Branch&Tag Menu Event=========
39 | EventsOn("Branch_M",()=>{
40 | store.dispatch(setOpenRepositoryBranch(true))
41 | })
42 | EventsOn("Tag_M",()=>{
43 | store.dispatch(setOpenRepositoryTag(true))
44 | })
45 |
46 |
47 | //===================other Menu Event=========
48 | EventsOn("Settings_Theme",()=>{
49 | store.dispatch(setOpenThemeSetting(true))
50 | })
51 | EventsOn("Settings_About",()=>{
52 | store.dispatch(setOpenAbout(true))
53 | })
54 |
55 |
--------------------------------------------------------------------------------
/frontend/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import {mainReducer} from "./sliceMain";
2 | import {configureStore} from '@reduxjs/toolkit';
3 | import {categoriesReducer} from "./sliceCategory";
4 | import {SettingState,settingReducer} from "./sliceSetting";
5 | import {CategoryState} from "./sliceCategory";
6 | import { Main } from "./sliceMain"
7 | import { DiffStateType,diffWorkReducer } from "./sliceWorkDiff"
8 | import { DiffCommitState,diffCommitReducer } from "./sliceCommitDiff"
9 |
10 | export type AsyncStatus = "loading"|"succeeded"|"failed"
11 |
12 | export type State = {
13 | categories:CategoryState,
14 | main:Main,
15 | setting:SettingState,
16 | diffWork:DiffStateType,
17 | diffCommit:DiffCommitState,
18 | }
19 |
20 | const store = configureStore({
21 | reducer: {
22 | main: mainReducer,
23 | categories: categoriesReducer,
24 | setting:settingReducer,
25 | diffWork:diffWorkReducer,
26 | diffCommit:diffCommitReducer
27 | }
28 | });
29 |
30 |
31 | export {store};
32 |
--------------------------------------------------------------------------------
/frontend/src/store/sliceCategory.ts:
--------------------------------------------------------------------------------
1 | import {warning} from '../utils/common'
2 | import {createSlice} from '@reduxjs/toolkit';
3 | import { PresetStatusColorType } from "antd/es/_util/colors"
4 | import type {PayloadAction} from '@reduxjs/toolkit'
5 | import {SaveJsonFile} from "../../wailsjs/go/main/App"
6 |
7 | export type Repository = {
8 | id: string
9 | name: string
10 | status: PresetStatusColorType
11 | path: string
12 | }
13 | export type Category = {
14 | name: string
15 | repositories: Repository[]
16 | }
17 |
18 | export type CategoryState = {
19 | val: Category[]
20 | }
21 |
22 | export type Droppable = {
23 | droppableId: string
24 | index: number
25 | }
26 |
27 | const saveJsonData = (c: Category[]) => {
28 | SaveJsonFile(JSON.stringify(c)).then(()=>{
29 | }).catch(e=>{
30 | warning(JSON.stringify(e))
31 | })
32 | }
33 |
34 | const initialState: CategoryState = {
35 | val:[
36 | {
37 | name: 'Default',
38 | repositories: []
39 | }
40 | ]
41 | };
42 |
43 |
44 | const categoriesSlice = createSlice({
45 | name: 'categoriesSlice',
46 | initialState,
47 | reducers: {
48 | setRepository(state, action: PayloadAction) {
49 | state.val = action.payload
50 | },
51 | addDefaultRepository(state, action: PayloadAction) {
52 |
53 | let defaultIndex = state.val.findIndex(r => r.name === "Default")
54 | state.val[defaultIndex].repositories.push(action.payload)
55 | saveJsonData(state.val)
56 | },
57 | delRepository(state, action: PayloadAction){
58 | for (let i = 0; i < state.val.length; i++) {
59 | let c = state.val[i]
60 | let index = c.repositories.findIndex(r=>r.id === action.payload)
61 | if(index!==-1){
62 | state.val[i].repositories.splice(index, 1)
63 | saveJsonData(state.val)
64 | return
65 | }
66 | }
67 | },
68 | editRepositoryName(state, action: PayloadAction<{ id:string,name:string }>){
69 | for (let i = 0; i < state.val.length; i++) {
70 | let c = state.val[i]
71 | let index = c.repositories.findIndex(r=>r.id === action.payload.id)
72 | if(index!==-1){
73 | state.val[i].repositories[index].name = action.payload.name
74 | saveJsonData(state.val)
75 | return
76 | }
77 | }
78 | },
79 | moveRepository(state, action: PayloadAction<{ source: Droppable, destination: Droppable }>) {
80 | let sourceIndex = state.val.findIndex(r => r.name === action.payload.source.droppableId)
81 | let destinationIndex = state.val.findIndex(r => r.name === action.payload.destination.droppableId)
82 | let r = state.val[sourceIndex].repositories.splice(action.payload.source.index, 1)
83 | state.val[destinationIndex].repositories.splice(action.payload.destination.index, 0, r[0])
84 |
85 | saveJsonData(state.val)
86 |
87 | },
88 | updateRepositoryStatus(state, {payload}: PayloadAction<{ id:string,status:PresetStatusColorType }>){
89 | for (let i = 0; i < state.val.length; i++) {
90 | let c = state.val[i]
91 | let index = c.repositories.findIndex(r=>r.id === payload.id)
92 | if(index!==-1){
93 | state.val[i].repositories[index].status = payload.status
94 | saveJsonData(state.val)
95 | return
96 | }
97 | }
98 |
99 | },
100 | addCategory(state, action: PayloadAction) {
101 | if (!action.payload) {
102 | return;
103 | }
104 | let has = state.val.find(r => r.name === action.payload)
105 | if (has) {
106 | warning(action.payload + " already exists.")
107 | return
108 | }
109 | state.val.push({
110 | name: action.payload,
111 | repositories: []
112 | })
113 | saveJsonData(state.val)
114 |
115 | },
116 | delCategory(state, action: PayloadAction) {
117 | let index = state.val.findIndex(c => c.name === action.payload)
118 | if (index === -1) {
119 | return
120 | }
121 | if (state.val[index].repositories.length !== 0) {
122 | warning("Repositories is not empty.")
123 | return;
124 | }
125 | state.val.splice(index, 1)
126 |
127 | saveJsonData(state.val)
128 | }
129 | },
130 | });
131 |
132 |
133 | export const categoriesReducer = categoriesSlice.reducer
134 | export const {
135 | addDefaultRepository,
136 | moveRepository,
137 | addCategory,
138 | delCategory,
139 | setRepository,
140 | delRepository,
141 | editRepositoryName,updateRepositoryStatus
142 | } = categoriesSlice.actions
143 |
144 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/frontend/src/store/sliceCommitDiff.ts:
--------------------------------------------------------------------------------
1 | import {createAsyncThunk, createSlice, PayloadAction} from "@reduxjs/toolkit";
2 | import {DiffCommit} from "../../wailsjs/go/repository/Repository"
3 | import {repository} from "../../wailsjs/go/models"
4 |
5 |
6 | export type DiffCommitState = {
7 | filesInfo: repository.ChangesFile[]
8 | statistics: string
9 | failedInfo:string|null
10 | commitId: string
11 | }
12 |
13 | const initialState: DiffCommitState = {
14 | filesInfo:[],
15 | statistics: "",
16 | failedInfo:null,
17 | commitId: ""
18 | }
19 |
20 | export const diffCommitAsync = createAsyncThunk(
21 | 'diffCommitSlice/diffCommit',
22 | async (arg,{rejectWithValue}) => {
23 | try {
24 | const r = await DiffCommit(arg.commitId,arg.commitId2)
25 | return {filesInfo:r.changesFiles||[], statistics: r.statistics, failedInfo:null, commitId:arg.commitId}
26 | }catch (err){
27 | return rejectWithValue({filesInfo:[], statistics: "", failedInfo:JSON.stringify(err), commitId:arg.commitId})
28 | }
29 |
30 | })
31 |
32 | const diffCommitSlice = createSlice({
33 | name: 'diffCommitSlice',
34 | initialState,
35 | reducers: {
36 | //删除全部使用的地方,通过订阅变动
37 | resetState: () => initialState,
38 | },
39 | extraReducers: builder => {
40 | builder
41 | .addCase(diffCommitAsync.fulfilled, (state, action) => {
42 | let { statistics,failedInfo,filesInfo,commitId } = action.payload
43 | state.statistics = statistics
44 | state.failedInfo = failedInfo
45 | state.filesInfo = filesInfo
46 | state.commitId = commitId
47 | // state = action.payload
48 | })
49 | .addCase(diffCommitAsync.rejected, (state, action) => {
50 | if(action.payload){
51 | let { statistics,failedInfo,filesInfo,commitId } = action.payload
52 | state.statistics = statistics
53 | state.failedInfo = failedInfo
54 | state.filesInfo = filesInfo
55 | state.commitId = commitId
56 | // state = action.payload
57 | }else {
58 | state.failedInfo = action.error.message||"";
59 | }
60 | });
61 | },
62 | });
63 |
64 | export const diffCommitReducer = diffCommitSlice.reducer
65 | export const {
66 | resetState
67 | } = diffCommitSlice.actions
68 |
--------------------------------------------------------------------------------
/frontend/src/store/sliceMain.ts:
--------------------------------------------------------------------------------
1 | import {repository} from "../../wailsjs/go/models"
2 | import {createSlice, PayloadAction,createSelector} from "@reduxjs/toolkit";
3 |
4 | export type Main = {
5 | selectedRepositoryId: string
6 | selectedRepositoryBranch: string
7 | currentlyRepositoryLocalBranch: repository.Branch[]
8 | currentlyRepositoryFileState: repository.FileStatus[]
9 | currentlyRepositoryCommits: repository.Commit[]
10 | }
11 |
12 |
13 | const initialState: Main = {
14 | selectedRepositoryId: "",
15 | selectedRepositoryBranch: "",
16 | currentlyRepositoryLocalBranch: [],
17 | currentlyRepositoryFileState: [],
18 | currentlyRepositoryCommits: []
19 | }
20 | const mainSlice = createSlice({
21 | name: 'mainSlice',
22 | initialState,
23 | reducers: {
24 | resetState(state) {
25 | state.selectedRepositoryBranch = ''
26 | state.currentlyRepositoryLocalBranch = []
27 | state.currentlyRepositoryFileState = []
28 | state.currentlyRepositoryCommits = []
29 | },
30 | setRepository(state, action: PayloadAction) {
31 | state.selectedRepositoryId = action.payload
32 | },
33 | setBranch(state, action: PayloadAction) {
34 | state.selectedRepositoryBranch = action.payload
35 | },
36 | setAllBranch(state, action: PayloadAction) {
37 | state.currentlyRepositoryLocalBranch = action.payload || []
38 | },
39 | setStatus(state, {payload}: PayloadAction) {
40 | state.currentlyRepositoryFileState = payload || []
41 | },
42 | setLog(state, action: PayloadAction){
43 | state.currentlyRepositoryCommits = action.payload||[]
44 | }
45 | },
46 | });
47 |
48 | export const mainReducer = mainSlice.reducer
49 | export const {
50 | setRepository,
51 | setBranch,
52 | setAllBranch,
53 | setStatus,
54 | setLog,
55 | resetState,
56 | } = mainSlice.actions
57 |
58 |
--------------------------------------------------------------------------------
/frontend/src/store/sliceSetting.ts:
--------------------------------------------------------------------------------
1 | import { theme,localThemeKey } from "../config/app"
2 | import {createSlice, PayloadAction} from "@reduxjs/toolkit";
3 | const localTheme = localStorage.getItem(localThemeKey)
4 | export type SettingState = {
5 | showRepositorySetting:boolean
6 | showRepositoryTag:boolean
7 | showRepositoryBranch:boolean
8 | showThemeSetting:boolean
9 | showAbout:boolean
10 | themeColor:string
11 | }
12 |
13 | const initialState: SettingState = {
14 | showRepositorySetting:false,
15 | showRepositoryTag:false,
16 | showRepositoryBranch:false,
17 | showThemeSetting:false,
18 | showAbout:false,
19 | themeColor:localTheme?localTheme:theme[0]
20 | }
21 | const settingSlice = createSlice({
22 | name: 'settingSlice',
23 | initialState,
24 | reducers: {
25 | setOpenRepositorySetting(state, action: PayloadAction){
26 | state.showRepositorySetting = action.payload
27 | },
28 | setOpenRepositoryTag(state, action: PayloadAction){
29 | state.showRepositoryTag = action.payload
30 | },
31 | setOpenRepositoryBranch(state, action: PayloadAction){
32 | state.showRepositoryBranch = action.payload
33 | },
34 | setOpenThemeSetting(state, action: PayloadAction){
35 | state.showThemeSetting = action.payload
36 | },
37 | setThemeColor(state, action: PayloadAction){
38 | state.themeColor = action.payload
39 | },
40 | setOpenAbout(state, action: PayloadAction){
41 | state.showAbout = action.payload
42 | }
43 | },
44 | });
45 |
46 | export const settingReducer = settingSlice.reducer
47 | export const {
48 | setOpenRepositorySetting,
49 | setOpenRepositoryTag,
50 | setOpenRepositoryBranch,
51 | setOpenThemeSetting,
52 | setOpenAbout,
53 | setThemeColor
54 | } = settingSlice.actions
55 |
56 |
--------------------------------------------------------------------------------
/frontend/src/store/sliceWorkDiff.ts:
--------------------------------------------------------------------------------
1 | import {repository} from "../../wailsjs/go/models"
2 | import {createSlice, createAsyncThunk} from "@reduxjs/toolkit";
3 | import {DiffWorkStage,ShowWorkTreeFile} from "../../wailsjs/go/repository/Repository"
4 |
5 | export enum DiffTypeEnum {
6 | "text",
7 | "img"
8 | }
9 |
10 | export type DiffStateType = {
11 | diffText: repository.DiffContent[]
12 | //diffImg
13 | diffType:DiffTypeEnum
14 | failedInfo:string|null
15 | fileStatus: repository.FileStatus|null
16 | }
17 |
18 |
19 | const initialState: DiffStateType = {
20 | diffText: [],
21 | diffType:DiffTypeEnum.text,
22 | failedInfo:null,
23 | fileStatus: null,
24 | }
25 |
26 | export const diffWorkTreeStageAsync = createAsyncThunk(
27 | 'diffWorkSlice/diffWorkTreeStage',
28 | async (fileStatus,{rejectWithValue}) => {
29 | try {
30 | if(fileStatus.worktree=="M"){
31 | const diffText = await DiffWorkStage(fileStatus.path);
32 | return {fileStatus,diffText,diffType:DiffTypeEnum.text,failedInfo:null}
33 | }else if (fileStatus.staging=="R") {
34 | const text = await ShowWorkTreeFile(fileStatus.path,1)
35 | return {fileStatus,diffText:text,diffType:DiffTypeEnum.text,failedInfo:null}
36 | }else if (fileStatus.staging=="A") {
37 | const text = await ShowWorkTreeFile(fileStatus.path,1)
38 | return {fileStatus,diffText:text,diffType:DiffTypeEnum.text,failedInfo:null}
39 | } else if (fileStatus.staging=="D"||fileStatus.worktree=="D") {
40 | return {fileStatus,diffText:[],diffType:DiffTypeEnum.text,failedInfo:"This file has been deleted."}
41 | }else if (fileStatus.staging=="?" && fileStatus.staging=="?") {
42 | return {fileStatus,diffText:[],diffType:DiffTypeEnum.text,failedInfo:"This file is not tracked by git."}
43 | }else {
44 | return {fileStatus,diffText:[],diffType:DiffTypeEnum.text,failedInfo:"Unprocessed file status."}
45 | }
46 | }catch (err){
47 | return rejectWithValue({fileStatus,diffText:[],diffType:DiffTypeEnum.text,failedInfo:JSON.stringify(err)});
48 | }
49 | }
50 | );
51 |
52 |
53 | const diffWorkSlice = createSlice({
54 | name: 'diffWorkSlice',
55 | initialState,
56 | reducers: {
57 | resetState: () => initialState,
58 | },
59 | extraReducers: builder => {
60 | builder
61 | .addCase(diffWorkTreeStageAsync.fulfilled, (state, action) => {
62 | let { fileStatus,diffText,diffType,failedInfo } = action.payload
63 | state.fileStatus = fileStatus;
64 | state.diffText = diffText;
65 | state.diffType = diffType;
66 | state.failedInfo = failedInfo;
67 | })
68 | .addCase(diffWorkTreeStageAsync.rejected, (state, action) => {
69 | if(action.payload){
70 | let { fileStatus,diffType,failedInfo } = action.payload
71 | state.fileStatus = fileStatus;
72 | state.diffText = [];
73 | state.diffType = diffType;
74 | state.failedInfo = failedInfo;
75 | }else {
76 | state.fileStatus = null;
77 | state.diffText = [];
78 | state.failedInfo = action.error.message||"";
79 | }
80 | });
81 | },
82 | });
83 |
84 | export const diffWorkReducer = diffWorkSlice.reducer
85 | export const {
86 | resetState
87 | } = diffWorkSlice.actions
88 |
--------------------------------------------------------------------------------
/frontend/src/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | background-color: rgba(103,58,183,.1);
3 | color: white;
4 | }
5 |
6 | body {
7 | margin: 0;
8 | color: black;
9 | overflow: hidden;
10 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
11 | "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
12 | sans-serif;
13 | }
14 |
15 |
16 | #app {
17 | height: 100vh;
18 | text-align: center;
19 | }
20 |
21 | .ant-dropdown-menu{
22 | max-height: 60vh;
23 | overflow-y: auto;
24 | }
25 | .branch{
26 | cursor: pointer;
27 | }
28 |
29 | .active-history-card-head .ant-badge-status-text{
30 | color: #fff!important;
31 | }
32 |
33 | .active-history-card-head .ant-space-item span{
34 | color: #fff!important;
35 | opacity: 1!important;
36 | }
--------------------------------------------------------------------------------
/frontend/src/utils/common.ts:
--------------------------------------------------------------------------------
1 | import {message, notification} from "antd";
2 | import {Clipboard} from "../../wailsjs/go/main/App";
3 |
4 | export const warning = (desc: string) => {
5 | notification.warning({
6 | message: `Tip`,
7 | description: desc,
8 | placement: "bottomLeft",
9 | });
10 | }
11 |
12 | export const success = (desc: string) => {
13 | notification.success({
14 | message: `Tip`,
15 | description: desc,
16 | placement: "bottomLeft",
17 | });
18 | }
19 |
20 |
21 |
22 | export const clipboard = async (r: string,tip= "Hash") => {
23 | try {
24 | await Clipboard(r)
25 | await message.success(`${tip} has been copied to clipboard.`)
26 | } catch (e) {
27 | warning(JSON.stringify(e))
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/src/utils/notification.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {notification} from "antd";
3 |
4 |
5 | export const successNotification = (out:any,style:React.CSSProperties)=>{
6 | notification.success({
7 | message: `Success`,
8 | description: {out}
,
9 | placement:"bottomLeft",
10 | style,
11 | });
12 | }
13 |
14 | export const warningNotification = (error:any,style:React.CSSProperties)=>{
15 | notification.warning({
16 | message: `Error`,
17 | description: {error}
,
18 | placement:"bottomLeft",
19 | style,
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/utils/repo.ts:
--------------------------------------------------------------------------------
1 | import {Category, Repository,updateRepositoryStatus} from "../store/sliceCategory";
2 | import {FileStatus} from "../../wailsjs/go/repository/Repository";
3 | import {Commits} from "../../wailsjs/go/repository/Repository";
4 | import {setLog, setStatus} from "../store/sliceMain";
5 | import {warning} from "./common";
6 | import {store} from "../store";
7 |
8 |
9 | export const getRepositoryById = (id: string, categories: Category[]): Repository | null => {
10 | for (let i = 0; i < categories.length; i++) {
11 | let category = categories[i]
12 | let repo = category.repositories.find(r => r.id === id)
13 | if (repo) return repo
14 | }
15 | return null
16 | }
17 |
18 | export const getRepositoryPathById = (id: string, categories: Category[]): string | null => {
19 | for (let i = 0; i < categories.length; i++) {
20 | let category = categories[i]
21 | let repo = category.repositories.find(r => r.id === id)
22 | if (repo) return repo.path
23 | }
24 | return null
25 | }
26 |
27 | export const updateWorkZone = async (id:string,branchName:string) => {
28 | try {
29 | const s = await FileStatus()
30 | store.dispatch(setStatus(s))
31 | setRepositoryStatus(id,Array.isArray(s)&&s.length!==0)
32 | const l = await Commits(branchName)
33 | store.dispatch(setLog(l))
34 | } catch (e) {
35 | console.log(e)
36 | warning("updateWorkZone:" + e)
37 | }
38 | }
39 |
40 | export const setRepositoryStatus = (id:string,isProcessing:boolean) => {
41 | store.dispatch(updateRepositoryStatus({id,status:isProcessing?'processing':'success'}))
42 | }
43 |
--------------------------------------------------------------------------------
/frontend/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": [
6 | "DOM",
7 | "DOM.Iterable",
8 | "ESNext"
9 | ],
10 | "allowJs": false,
11 | "skipLibCheck": true,
12 | "esModuleInterop": false,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "module": "ESNext",
17 | "moduleResolution": "Node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ],
26 | "references": [
27 | {
28 | "path": "./tsconfig.node.json"
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": [
9 | "vite.config.ts"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/vite.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------
/frontend/wailsjs/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xusenlin/git-helper/19e88fa5f00ec16a87de17963883de7ddfc176dc/frontend/wailsjs/.DS_Store
--------------------------------------------------------------------------------
/frontend/wailsjs/go/main/App.d.ts:
--------------------------------------------------------------------------------
1 | // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
2 | // This file is automatically generated. DO NOT EDIT
3 |
4 | export function Clipboard(arg1:string):Promise;
5 |
6 | export function IsGitRepository(arg1:string):Promise;
7 |
8 | export function MessageDialog(arg1:string,arg2:string):Promise;
9 |
10 | export function ReadJsonFile():Promise;
11 |
12 | export function SaveJsonFile(arg1:string):Promise;
13 |
14 | export function Sha256(arg1:string):Promise;
15 |
--------------------------------------------------------------------------------
/frontend/wailsjs/go/main/App.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
3 | // This file is automatically generated. DO NOT EDIT
4 |
5 | export function Clipboard(arg1) {
6 | return window['go']['main']['App']['Clipboard'](arg1);
7 | }
8 |
9 | export function IsGitRepository(arg1) {
10 | return window['go']['main']['App']['IsGitRepository'](arg1);
11 | }
12 |
13 | export function MessageDialog(arg1, arg2) {
14 | return window['go']['main']['App']['MessageDialog'](arg1, arg2);
15 | }
16 |
17 | export function ReadJsonFile() {
18 | return window['go']['main']['App']['ReadJsonFile']();
19 | }
20 |
21 | export function SaveJsonFile(arg1) {
22 | return window['go']['main']['App']['SaveJsonFile'](arg1);
23 | }
24 |
25 | export function Sha256(arg1) {
26 | return window['go']['main']['App']['Sha256'](arg1);
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/wailsjs/go/models.ts:
--------------------------------------------------------------------------------
1 | export namespace repository {
2 |
3 | export class Branch {
4 | hash: string;
5 | name: string;
6 | refName: string;
7 | upstream: string;
8 |
9 | static createFrom(source: any = {}) {
10 | return new Branch(source);
11 | }
12 |
13 | constructor(source: any = {}) {
14 | if ('string' === typeof source) source = JSON.parse(source);
15 | this.hash = source["hash"];
16 | this.name = source["name"];
17 | this.refName = source["refName"];
18 | this.upstream = source["upstream"];
19 | }
20 | }
21 | export class ChangesFile {
22 | fileName: string;
23 | index: number;
24 | desc: string;
25 |
26 | static createFrom(source: any = {}) {
27 | return new ChangesFile(source);
28 | }
29 |
30 | constructor(source: any = {}) {
31 | if ('string' === typeof source) source = JSON.parse(source);
32 | this.fileName = source["fileName"];
33 | this.index = source["index"];
34 | this.desc = source["desc"];
35 | }
36 | }
37 | export class Signature {
38 | name: string;
39 | email: string;
40 | when: string;
41 |
42 | static createFrom(source: any = {}) {
43 | return new Signature(source);
44 | }
45 |
46 | constructor(source: any = {}) {
47 | if ('string' === typeof source) source = JSON.parse(source);
48 | this.name = source["name"];
49 | this.email = source["email"];
50 | this.when = source["when"];
51 | }
52 | }
53 | export class Commit {
54 | hash: string;
55 | author: string;
56 | committer: Signature;
57 | message: string;
58 | treeHash: string;
59 | parentHashes: string;
60 | isRemoteSync: boolean;
61 |
62 | static createFrom(source: any = {}) {
63 | return new Commit(source);
64 | }
65 |
66 | constructor(source: any = {}) {
67 | if ('string' === typeof source) source = JSON.parse(source);
68 | this.hash = source["hash"];
69 | this.author = source["author"];
70 | this.committer = this.convertValues(source["committer"], Signature);
71 | this.message = source["message"];
72 | this.treeHash = source["treeHash"];
73 | this.parentHashes = source["parentHashes"];
74 | this.isRemoteSync = source["isRemoteSync"];
75 | }
76 |
77 | convertValues(a: any, classs: any, asMap: boolean = false): any {
78 | if (!a) {
79 | return a;
80 | }
81 | if (a.slice) {
82 | return (a as any[]).map(elem => this.convertValues(elem, classs));
83 | } else if ("object" === typeof a) {
84 | if (asMap) {
85 | for (const key of Object.keys(a)) {
86 | a[key] = new classs(a[key]);
87 | }
88 | return a;
89 | }
90 | return new classs(a);
91 | }
92 | return a;
93 | }
94 | }
95 | export class DiffCommitInfo {
96 | changesFiles: ChangesFile[];
97 | statistics: string;
98 |
99 | static createFrom(source: any = {}) {
100 | return new DiffCommitInfo(source);
101 | }
102 |
103 | constructor(source: any = {}) {
104 | if ('string' === typeof source) source = JSON.parse(source);
105 | this.changesFiles = this.convertValues(source["changesFiles"], ChangesFile);
106 | this.statistics = source["statistics"];
107 | }
108 |
109 | convertValues(a: any, classs: any, asMap: boolean = false): any {
110 | if (!a) {
111 | return a;
112 | }
113 | if (a.slice) {
114 | return (a as any[]).map(elem => this.convertValues(elem, classs));
115 | } else if ("object" === typeof a) {
116 | if (asMap) {
117 | for (const key of Object.keys(a)) {
118 | a[key] = new classs(a[key]);
119 | }
120 | return a;
121 | }
122 | return new classs(a);
123 | }
124 | return a;
125 | }
126 | }
127 | export class DiffContent {
128 | content: string;
129 | type: number;
130 | index: number;
131 |
132 | static createFrom(source: any = {}) {
133 | return new DiffContent(source);
134 | }
135 |
136 | constructor(source: any = {}) {
137 | if ('string' === typeof source) source = JSON.parse(source);
138 | this.content = source["content"];
139 | this.type = source["type"];
140 | this.index = source["index"];
141 | }
142 | }
143 | export class FileStatus {
144 | name: string;
145 | path: string;
146 | staging: string;
147 | worktree: string;
148 |
149 | static createFrom(source: any = {}) {
150 | return new FileStatus(source);
151 | }
152 |
153 | constructor(source: any = {}) {
154 | if ('string' === typeof source) source = JSON.parse(source);
155 | this.name = source["name"];
156 | this.path = source["path"];
157 | this.staging = source["staging"];
158 | this.worktree = source["worktree"];
159 | }
160 | }
161 | export class LogDesc {
162 | tag: string[];
163 | branch: string[];
164 |
165 | static createFrom(source: any = {}) {
166 | return new LogDesc(source);
167 | }
168 |
169 | constructor(source: any = {}) {
170 | if ('string' === typeof source) source = JSON.parse(source);
171 | this.tag = source["tag"];
172 | this.branch = source["branch"];
173 | }
174 | }
175 | export class Log {
176 | hash: string;
177 | parentHashes: string[];
178 | desc: LogDesc;
179 | author: string;
180 | message: string;
181 |
182 | static createFrom(source: any = {}) {
183 | return new Log(source);
184 | }
185 |
186 | constructor(source: any = {}) {
187 | if ('string' === typeof source) source = JSON.parse(source);
188 | this.hash = source["hash"];
189 | this.parentHashes = source["parentHashes"];
190 | this.desc = this.convertValues(source["desc"], LogDesc);
191 | this.author = source["author"];
192 | this.message = source["message"];
193 | }
194 |
195 | convertValues(a: any, classs: any, asMap: boolean = false): any {
196 | if (!a) {
197 | return a;
198 | }
199 | if (a.slice) {
200 | return (a as any[]).map(elem => this.convertValues(elem, classs));
201 | } else if ("object" === typeof a) {
202 | if (asMap) {
203 | for (const key of Object.keys(a)) {
204 | a[key] = new classs(a[key]);
205 | }
206 | return a;
207 | }
208 | return new classs(a);
209 | }
210 | return a;
211 | }
212 | }
213 |
214 | export class MergeResult {
215 | kind: number;
216 | count: number;
217 |
218 | static createFrom(source: any = {}) {
219 | return new MergeResult(source);
220 | }
221 |
222 | constructor(source: any = {}) {
223 | if ('string' === typeof source) source = JSON.parse(source);
224 | this.kind = source["kind"];
225 | this.count = source["count"];
226 | }
227 | }
228 |
229 | export class Tag {
230 | name: string;
231 | refName: string;
232 | type: string;
233 | message: string;
234 | hash: string;
235 | time: string;
236 |
237 | static createFrom(source: any = {}) {
238 | return new Tag(source);
239 | }
240 |
241 | constructor(source: any = {}) {
242 | if ('string' === typeof source) source = JSON.parse(source);
243 | this.name = source["name"];
244 | this.refName = source["refName"];
245 | this.type = source["type"];
246 | this.message = source["message"];
247 | this.hash = source["hash"];
248 | this.time = source["time"];
249 | }
250 | }
251 |
252 | }
253 |
254 |
--------------------------------------------------------------------------------
/frontend/wailsjs/go/repository/Repository.d.ts:
--------------------------------------------------------------------------------
1 | // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
2 | // This file is automatically generated. DO NOT EDIT
3 | import {repository} from '../models';
4 |
5 | export function AddBranch(arg1:string):Promise;
6 |
7 | export function Commit(arg1:string,arg2:string,arg3:Array):Promise;
8 |
9 | export function Commits(arg1:string):Promise>;
10 |
11 | export function CommitsLog():Promise>;
12 |
13 | export function CreateTag(arg1:string,arg2:string):Promise;
14 |
15 | export function CreateTagByCommitId(arg1:string,arg2:string):Promise;
16 |
17 | export function DelBranch(arg1:string,arg2:boolean):Promise;
18 |
19 | export function DelTag(arg1:string,arg2:boolean):Promise;
20 |
21 | export function DiffCommit(arg1:string,arg2:string):Promise;
22 |
23 | export function DiffWorkStage(arg1:string):Promise>;
24 |
25 | export function DiscardChanges(arg1:string):Promise;
26 |
27 | export function FileStatus():Promise>;
28 |
29 | export function GetAllBranch():Promise>;
30 |
31 | export function GetBranchHash(arg1:string):Promise;
32 |
33 | export function GetCurrentBranch():Promise;
34 |
35 | export function GetLocalBranch():Promise>;
36 |
37 | export function GitPull():Promise;
38 |
39 | export function GitPush():Promise;
40 |
41 | export function GitRemoteUrl(arg1:string):Promise;
42 |
43 | export function IsRemoteRepo():Promise;
44 |
45 | export function MergeCommit(arg1:string,arg2:string):Promise;
46 |
47 | export function MergeRebase(arg1:string,arg2:string):Promise;
48 |
49 | export function MergeSquash(arg1:string,arg2:string):Promise;
50 |
51 | export function OpenFileManage():Promise;
52 |
53 | export function OpenTerminal():Promise;
54 |
55 | export function PreMergeResult(arg1:string,arg2:string):Promise;
56 |
57 | export function PushBranch(arg1:string):Promise;
58 |
59 | export function PushTag(arg1:string):Promise;
60 |
61 | export function RemoteTags():Promise>;
62 |
63 | export function RunCmdInRepository(arg1:string,arg2:Array):Promise;
64 |
65 | export function ShowWorkTreeFile(arg1:string,arg2:number):Promise>;
66 |
67 | export function SwitchBranch(arg1:string):Promise;
68 |
69 | export function SwitchRepository(arg1:string):Promise;
70 |
71 | export function Tags():Promise>;
72 |
--------------------------------------------------------------------------------
/frontend/wailsjs/go/repository/Repository.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
3 | // This file is automatically generated. DO NOT EDIT
4 |
5 | export function AddBranch(arg1) {
6 | return window['go']['repository']['Repository']['AddBranch'](arg1);
7 | }
8 |
9 | export function Commit(arg1, arg2, arg3) {
10 | return window['go']['repository']['Repository']['Commit'](arg1, arg2, arg3);
11 | }
12 |
13 | export function Commits(arg1) {
14 | return window['go']['repository']['Repository']['Commits'](arg1);
15 | }
16 |
17 | export function CommitsLog() {
18 | return window['go']['repository']['Repository']['CommitsLog']();
19 | }
20 |
21 | export function CreateTag(arg1, arg2) {
22 | return window['go']['repository']['Repository']['CreateTag'](arg1, arg2);
23 | }
24 |
25 | export function CreateTagByCommitId(arg1, arg2) {
26 | return window['go']['repository']['Repository']['CreateTagByCommitId'](arg1, arg2);
27 | }
28 |
29 | export function DelBranch(arg1, arg2) {
30 | return window['go']['repository']['Repository']['DelBranch'](arg1, arg2);
31 | }
32 |
33 | export function DelTag(arg1, arg2) {
34 | return window['go']['repository']['Repository']['DelTag'](arg1, arg2);
35 | }
36 |
37 | export function DiffCommit(arg1, arg2) {
38 | return window['go']['repository']['Repository']['DiffCommit'](arg1, arg2);
39 | }
40 |
41 | export function DiffWorkStage(arg1) {
42 | return window['go']['repository']['Repository']['DiffWorkStage'](arg1);
43 | }
44 |
45 | export function DiscardChanges(arg1) {
46 | return window['go']['repository']['Repository']['DiscardChanges'](arg1);
47 | }
48 |
49 | export function FileStatus() {
50 | return window['go']['repository']['Repository']['FileStatus']();
51 | }
52 |
53 | export function GetAllBranch() {
54 | return window['go']['repository']['Repository']['GetAllBranch']();
55 | }
56 |
57 | export function GetBranchHash(arg1) {
58 | return window['go']['repository']['Repository']['GetBranchHash'](arg1);
59 | }
60 |
61 | export function GetCurrentBranch() {
62 | return window['go']['repository']['Repository']['GetCurrentBranch']();
63 | }
64 |
65 | export function GetLocalBranch() {
66 | return window['go']['repository']['Repository']['GetLocalBranch']();
67 | }
68 |
69 | export function GitPull() {
70 | return window['go']['repository']['Repository']['GitPull']();
71 | }
72 |
73 | export function GitPush() {
74 | return window['go']['repository']['Repository']['GitPush']();
75 | }
76 |
77 | export function GitRemoteUrl(arg1) {
78 | return window['go']['repository']['Repository']['GitRemoteUrl'](arg1);
79 | }
80 |
81 | export function IsRemoteRepo() {
82 | return window['go']['repository']['Repository']['IsRemoteRepo']();
83 | }
84 |
85 | export function MergeCommit(arg1, arg2) {
86 | return window['go']['repository']['Repository']['MergeCommit'](arg1, arg2);
87 | }
88 |
89 | export function MergeRebase(arg1, arg2) {
90 | return window['go']['repository']['Repository']['MergeRebase'](arg1, arg2);
91 | }
92 |
93 | export function MergeSquash(arg1, arg2) {
94 | return window['go']['repository']['Repository']['MergeSquash'](arg1, arg2);
95 | }
96 |
97 | export function OpenFileManage() {
98 | return window['go']['repository']['Repository']['OpenFileManage']();
99 | }
100 |
101 | export function OpenTerminal() {
102 | return window['go']['repository']['Repository']['OpenTerminal']();
103 | }
104 |
105 | export function PreMergeResult(arg1, arg2) {
106 | return window['go']['repository']['Repository']['PreMergeResult'](arg1, arg2);
107 | }
108 |
109 | export function PushBranch(arg1) {
110 | return window['go']['repository']['Repository']['PushBranch'](arg1);
111 | }
112 |
113 | export function PushTag(arg1) {
114 | return window['go']['repository']['Repository']['PushTag'](arg1);
115 | }
116 |
117 | export function RemoteTags() {
118 | return window['go']['repository']['Repository']['RemoteTags']();
119 | }
120 |
121 | export function RunCmdInRepository(arg1, arg2) {
122 | return window['go']['repository']['Repository']['RunCmdInRepository'](arg1, arg2);
123 | }
124 |
125 | export function ShowWorkTreeFile(arg1, arg2) {
126 | return window['go']['repository']['Repository']['ShowWorkTreeFile'](arg1, arg2);
127 | }
128 |
129 | export function SwitchBranch(arg1) {
130 | return window['go']['repository']['Repository']['SwitchBranch'](arg1);
131 | }
132 |
133 | export function SwitchRepository(arg1) {
134 | return window['go']['repository']['Repository']['SwitchRepository'](arg1);
135 | }
136 |
137 | export function Tags() {
138 | return window['go']['repository']['Repository']['Tags']();
139 | }
140 |
--------------------------------------------------------------------------------
/frontend/wailsjs/runtime/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@wailsapp/runtime",
3 | "version": "2.0.0",
4 | "description": "Wails Javascript runtime library",
5 | "main": "runtime.js",
6 | "types": "runtime.d.ts",
7 | "scripts": {
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/wailsapp/wails.git"
12 | },
13 | "keywords": [
14 | "Wails",
15 | "Javascript",
16 | "Go"
17 | ],
18 | "author": "Lea Anthony ",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/wailsapp/wails/issues"
22 | },
23 | "homepage": "https://github.com/wailsapp/wails#readme"
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/wailsjs/runtime/runtime.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | _ __ _ __
3 | | | / /___ _(_) /____
4 | | | /| / / __ `/ / / ___/
5 | | |/ |/ / /_/ / / (__ )
6 | |__/|__/\__,_/_/_/____/
7 | The electron alternative for Go
8 | (c) Lea Anthony 2019-present
9 | */
10 |
11 | export interface Position {
12 | x: number;
13 | y: number;
14 | }
15 |
16 | export interface Size {
17 | w: number;
18 | h: number;
19 | }
20 |
21 | export interface Screen {
22 | isCurrent: boolean;
23 | isPrimary: boolean;
24 | width : number
25 | height : number
26 | }
27 |
28 | // Environment information such as platform, buildtype, ...
29 | export interface EnvironmentInfo {
30 | buildType: string;
31 | platform: string;
32 | arch: string;
33 | }
34 |
35 | // [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
36 | // emits the given event. Optional data may be passed with the event.
37 | // This will trigger any event listeners.
38 | export function EventsEmit(eventName: string, ...data: any): void;
39 |
40 | // [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
41 | export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
42 |
43 | // [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
44 | // sets up a listener for the given event name, but will only trigger a given number times.
45 | export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
46 |
47 | // [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
48 | // sets up a listener for the given event name, but will only trigger once.
49 | export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
50 |
51 | // [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
52 | // unregisters the listener for the given event name.
53 | export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
54 |
55 | // [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
56 | // unregisters all listeners.
57 | export function EventsOffAll(): void;
58 |
59 | // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
60 | // logs the given message as a raw message
61 | export function LogPrint(message: string): void;
62 |
63 | // [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
64 | // logs the given message at the `trace` log level.
65 | export function LogTrace(message: string): void;
66 |
67 | // [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
68 | // logs the given message at the `debug` log level.
69 | export function LogDebug(message: string): void;
70 |
71 | // [LogError](https://wails.io/docs/reference/runtime/log#logerror)
72 | // logs the given message at the `error` log level.
73 | export function LogError(message: string): void;
74 |
75 | // [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
76 | // logs the given message at the `fatal` log level.
77 | // The application will quit after calling this method.
78 | export function LogFatal(message: string): void;
79 |
80 | // [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
81 | // logs the given message at the `info` log level.
82 | export function LogInfo(message: string): void;
83 |
84 | // [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
85 | // logs the given message at the `warning` log level.
86 | export function LogWarning(message: string): void;
87 |
88 | // [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
89 | // Forces a reload by the main application as well as connected browsers.
90 | export function WindowReload(): void;
91 |
92 | // [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
93 | // Reloads the application frontend.
94 | export function WindowReloadApp(): void;
95 |
96 | // [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
97 | // Sets the window AlwaysOnTop or not on top.
98 | export function WindowSetAlwaysOnTop(b: boolean): void;
99 |
100 | // [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
101 | // *Windows only*
102 | // Sets window theme to system default (dark/light).
103 | export function WindowSetSystemDefaultTheme(): void;
104 |
105 | // [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
106 | // *Windows only*
107 | // Sets window to light theme.
108 | export function WindowSetLightTheme(): void;
109 |
110 | // [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
111 | // *Windows only*
112 | // Sets window to dark theme.
113 | export function WindowSetDarkTheme(): void;
114 |
115 | // [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
116 | // Centers the window on the monitor the window is currently on.
117 | export function WindowCenter(): void;
118 |
119 | // [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
120 | // Sets the text in the window title bar.
121 | export function WindowSetTitle(title: string): void;
122 |
123 | // [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
124 | // Makes the window full screen.
125 | export function WindowFullscreen(): void;
126 |
127 | // [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
128 | // Restores the previous window dimensions and position prior to full screen.
129 | export function WindowUnfullscreen(): void;
130 |
131 | // [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
132 | // Returns the state of the window, i.e. whether the window is in full screen mode or not.
133 | export function WindowIsFullscreen(): Promise;
134 |
135 | // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
136 | // Sets the width and height of the window.
137 | export function WindowSetSize(width: number, height: number): Promise;
138 |
139 | // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
140 | // Gets the width and height of the window.
141 | export function WindowGetSize(): Promise;
142 |
143 | // [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
144 | // Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
145 | // Setting a size of 0,0 will disable this constraint.
146 | export function WindowSetMaxSize(width: number, height: number): void;
147 |
148 | // [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
149 | // Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
150 | // Setting a size of 0,0 will disable this constraint.
151 | export function WindowSetMinSize(width: number, height: number): void;
152 |
153 | // [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
154 | // Sets the window position relative to the monitor the window is currently on.
155 | export function WindowSetPosition(x: number, y: number): void;
156 |
157 | // [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
158 | // Gets the window position relative to the monitor the window is currently on.
159 | export function WindowGetPosition(): Promise;
160 |
161 | // [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
162 | // Hides the window.
163 | export function WindowHide(): void;
164 |
165 | // [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
166 | // Shows the window, if it is currently hidden.
167 | export function WindowShow(): void;
168 |
169 | // [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
170 | // Maximises the window to fill the screen.
171 | export function WindowMaximise(): void;
172 |
173 | // [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
174 | // Toggles between Maximised and UnMaximised.
175 | export function WindowToggleMaximise(): void;
176 |
177 | // [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
178 | // Restores the window to the dimensions and position prior to maximising.
179 | export function WindowUnmaximise(): void;
180 |
181 | // [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
182 | // Returns the state of the window, i.e. whether the window is maximised or not.
183 | export function WindowIsMaximised(): Promise;
184 |
185 | // [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
186 | // Minimises the window.
187 | export function WindowMinimise(): void;
188 |
189 | // [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
190 | // Restores the window to the dimensions and position prior to minimising.
191 | export function WindowUnminimise(): void;
192 |
193 | // [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
194 | // Returns the state of the window, i.e. whether the window is minimised or not.
195 | export function WindowIsMinimised(): Promise;
196 |
197 | // [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
198 | // Returns the state of the window, i.e. whether the window is normal or not.
199 | export function WindowIsNormal(): Promise;
200 |
201 | // [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
202 | // Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
203 | export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
204 |
205 | // [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
206 | // Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
207 | export function ScreenGetAll(): Promise;
208 |
209 | // [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
210 | // Opens the given URL in the system browser.
211 | export function BrowserOpenURL(url: string): void;
212 |
213 | // [Environment](https://wails.io/docs/reference/runtime/intro#environment)
214 | // Returns information about the environment
215 | export function Environment(): Promise;
216 |
217 | // [Quit](https://wails.io/docs/reference/runtime/intro#quit)
218 | // Quits the application.
219 | export function Quit(): void;
220 |
221 | // [Hide](https://wails.io/docs/reference/runtime/intro#hide)
222 | // Hides the application.
223 | export function Hide(): void;
224 |
225 | // [Show](https://wails.io/docs/reference/runtime/intro#show)
226 | // Shows the application.
227 | export function Show(): void;
228 |
--------------------------------------------------------------------------------
/frontend/wailsjs/runtime/runtime.js:
--------------------------------------------------------------------------------
1 | /*
2 | _ __ _ __
3 | | | / /___ _(_) /____
4 | | | /| / / __ `/ / / ___/
5 | | |/ |/ / /_/ / / (__ )
6 | |__/|__/\__,_/_/_/____/
7 | The electron alternative for Go
8 | (c) Lea Anthony 2019-present
9 | */
10 |
11 | export function LogPrint(message) {
12 | window.runtime.LogPrint(message);
13 | }
14 |
15 | export function LogTrace(message) {
16 | window.runtime.LogTrace(message);
17 | }
18 |
19 | export function LogDebug(message) {
20 | window.runtime.LogDebug(message);
21 | }
22 |
23 | export function LogInfo(message) {
24 | window.runtime.LogInfo(message);
25 | }
26 |
27 | export function LogWarning(message) {
28 | window.runtime.LogWarning(message);
29 | }
30 |
31 | export function LogError(message) {
32 | window.runtime.LogError(message);
33 | }
34 |
35 | export function LogFatal(message) {
36 | window.runtime.LogFatal(message);
37 | }
38 |
39 | export function EventsOnMultiple(eventName, callback, maxCallbacks) {
40 | window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
41 | }
42 |
43 | export function EventsOn(eventName, callback) {
44 | EventsOnMultiple(eventName, callback, -1);
45 | }
46 |
47 | export function EventsOff(eventName, ...additionalEventNames) {
48 | return window.runtime.EventsOff(eventName, ...additionalEventNames);
49 | }
50 |
51 | export function EventsOnce(eventName, callback) {
52 | EventsOnMultiple(eventName, callback, 1);
53 | }
54 |
55 | export function EventsEmit(eventName) {
56 | let args = [eventName].slice.call(arguments);
57 | return window.runtime.EventsEmit.apply(null, args);
58 | }
59 |
60 | export function WindowReload() {
61 | window.runtime.WindowReload();
62 | }
63 |
64 | export function WindowReloadApp() {
65 | window.runtime.WindowReloadApp();
66 | }
67 |
68 | export function WindowSetAlwaysOnTop(b) {
69 | window.runtime.WindowSetAlwaysOnTop(b);
70 | }
71 |
72 | export function WindowSetSystemDefaultTheme() {
73 | window.runtime.WindowSetSystemDefaultTheme();
74 | }
75 |
76 | export function WindowSetLightTheme() {
77 | window.runtime.WindowSetLightTheme();
78 | }
79 |
80 | export function WindowSetDarkTheme() {
81 | window.runtime.WindowSetDarkTheme();
82 | }
83 |
84 | export function WindowCenter() {
85 | window.runtime.WindowCenter();
86 | }
87 |
88 | export function WindowSetTitle(title) {
89 | window.runtime.WindowSetTitle(title);
90 | }
91 |
92 | export function WindowFullscreen() {
93 | window.runtime.WindowFullscreen();
94 | }
95 |
96 | export function WindowUnfullscreen() {
97 | window.runtime.WindowUnfullscreen();
98 | }
99 |
100 | export function WindowIsFullscreen() {
101 | return window.runtime.WindowIsFullscreen();
102 | }
103 |
104 | export function WindowGetSize() {
105 | return window.runtime.WindowGetSize();
106 | }
107 |
108 | export function WindowSetSize(width, height) {
109 | window.runtime.WindowSetSize(width, height);
110 | }
111 |
112 | export function WindowSetMaxSize(width, height) {
113 | window.runtime.WindowSetMaxSize(width, height);
114 | }
115 |
116 | export function WindowSetMinSize(width, height) {
117 | window.runtime.WindowSetMinSize(width, height);
118 | }
119 |
120 | export function WindowSetPosition(x, y) {
121 | window.runtime.WindowSetPosition(x, y);
122 | }
123 |
124 | export function WindowGetPosition() {
125 | return window.runtime.WindowGetPosition();
126 | }
127 |
128 | export function WindowHide() {
129 | window.runtime.WindowHide();
130 | }
131 |
132 | export function WindowShow() {
133 | window.runtime.WindowShow();
134 | }
135 |
136 | export function WindowMaximise() {
137 | window.runtime.WindowMaximise();
138 | }
139 |
140 | export function WindowToggleMaximise() {
141 | window.runtime.WindowToggleMaximise();
142 | }
143 |
144 | export function WindowUnmaximise() {
145 | window.runtime.WindowUnmaximise();
146 | }
147 |
148 | export function WindowIsMaximised() {
149 | return window.runtime.WindowIsMaximised();
150 | }
151 |
152 | export function WindowMinimise() {
153 | window.runtime.WindowMinimise();
154 | }
155 |
156 | export function WindowUnminimise() {
157 | window.runtime.WindowUnminimise();
158 | }
159 |
160 | export function WindowSetBackgroundColour(R, G, B, A) {
161 | window.runtime.WindowSetBackgroundColour(R, G, B, A);
162 | }
163 |
164 | export function ScreenGetAll() {
165 | return window.runtime.ScreenGetAll();
166 | }
167 |
168 | export function WindowIsMinimised() {
169 | return window.runtime.WindowIsMinimised();
170 | }
171 |
172 | export function WindowIsNormal() {
173 | return window.runtime.WindowIsNormal();
174 | }
175 |
176 | export function BrowserOpenURL(url) {
177 | window.runtime.BrowserOpenURL(url);
178 | }
179 |
180 | export function Environment() {
181 | return window.runtime.Environment();
182 | }
183 |
184 | export function Quit() {
185 | window.runtime.Quit();
186 | }
187 |
188 | export function Hide() {
189 | window.runtime.Hide();
190 | }
191 |
192 | export function Show() {
193 | window.runtime.Show();
194 | }
195 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module git-helper
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/atotto/clipboard v0.1.4
7 | github.com/wailsapp/wails/v2 v2.3.1
8 | )
9 |
10 | require (
11 | github.com/bep/debounce v1.2.1 // indirect
12 | github.com/go-ole/go-ole v1.2.6 // indirect
13 | github.com/google/uuid v1.1.2 // indirect
14 | github.com/imdario/mergo v0.3.13 // indirect
15 | github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
16 | github.com/labstack/echo/v4 v4.9.0 // indirect
17 | github.com/labstack/gommon v0.3.1 // indirect
18 | github.com/leaanthony/go-ansi-parser v1.0.1 // indirect
19 | github.com/leaanthony/gosod v1.0.3 // indirect
20 | github.com/leaanthony/slicer v1.5.0 // indirect
21 | github.com/mattn/go-colorable v0.1.11 // indirect
22 | github.com/mattn/go-isatty v0.0.14 // indirect
23 | github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect
24 | github.com/pkg/errors v0.9.1 // indirect
25 | github.com/samber/lo v1.27.1 // indirect
26 | github.com/tkrajina/go-reflector v0.5.5 // indirect
27 | github.com/valyala/bytebufferpool v1.0.0 // indirect
28 | github.com/valyala/fasttemplate v1.2.1 // indirect
29 | github.com/wailsapp/mimetype v1.4.1 // indirect
30 | golang.org/x/crypto v0.4.0 // indirect
31 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
32 | golang.org/x/net v0.4.0 // indirect
33 | golang.org/x/sys v0.3.0 // indirect
34 | golang.org/x/text v0.5.0 // indirect
35 | )
36 |
37 | // replace github.com/wailsapp/wails/v2 v2.2.0 => /Users/xusenlin/go/pkg/mod
38 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
2 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
3 | github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
4 | github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
9 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
10 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
11 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
12 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
13 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
14 | github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
15 | github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
16 | github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=
17 | github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
18 | github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
19 | github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
20 | github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
21 | github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
22 | github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
23 | github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
24 | github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
25 | github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
26 | github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
27 | github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
28 | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
29 | github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
30 | github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
31 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
32 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
33 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
34 | github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc=
35 | github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
36 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
37 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
38 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
39 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
40 | github.com/samber/lo v1.27.1 h1:sTXwkRiIFIQG+G0HeAvOEnGjqWeWtI9cg5/n51KrxPg=
41 | github.com/samber/lo v1.27.1/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg=
42 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
43 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
44 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
45 | github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
46 | github.com/tkrajina/go-reflector v0.5.5 h1:gwoQFNye30Kk7NrExj8zm3zFtrGPqOkzFMLuQZg1DtQ=
47 | github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
48 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
49 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
50 | github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
51 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
52 | github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
53 | github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
54 | github.com/wailsapp/wails/v2 v2.3.1 h1:ZJz+pyIBKyASkgO8JO31NuHO1gTTHmvwiHYHwei1CqM=
55 | github.com/wailsapp/wails/v2 v2.3.1/go.mod h1:zlNLI0E2c2qA6miiuAHtp0Bac8FaGH0tlhA19OssR/8=
56 | golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
57 | golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
58 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
59 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
60 | golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
61 | golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
62 | golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
63 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
64 | golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
65 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
66 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
67 | golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
68 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
69 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
70 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
71 | golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
72 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
73 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
74 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
75 | golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
76 | golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
77 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
78 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
79 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
80 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
81 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
82 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
83 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "embed"
5 | "git-helper/repository"
6 | "github.com/wailsapp/wails/v2"
7 | "github.com/wailsapp/wails/v2/pkg/options"
8 | "github.com/wailsapp/wails/v2/pkg/options/assetserver"
9 | "github.com/wailsapp/wails/v2/pkg/options/mac"
10 | "github.com/wailsapp/wails/v2/pkg/options/windows"
11 | )
12 |
13 | //go:embed all:frontend/dist
14 | var assets embed.FS
15 |
16 | func main() {
17 | // Create an instance of the app structure
18 | app := NewApp()
19 | repo := repository.New()
20 | app.setRepository(repo)
21 |
22 | // Create application with options
23 | err := wails.Run(&options.App{
24 | Title: "GitUI",
25 | Width: 1300,
26 | Height: 800,
27 | AssetServer: &assetserver.Options{
28 | Assets: assets,
29 | },
30 | //BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 19},
31 | OnStartup: app.startup,
32 | Menu: app.menu(),
33 | Windows: &windows.Options{
34 | WebviewIsTransparent: false,
35 | WindowIsTranslucent: false,
36 | DisableWindowIcon: false,
37 | // DisableFramelessWindowDecorations: false,
38 | WebviewUserDataPath: "",
39 | },
40 | Mac: &mac.Options{
41 | WebviewIsTransparent: true,
42 | WindowIsTranslucent: true,
43 | TitleBar: mac.TitleBarHiddenInset(),
44 | Appearance: mac.NSAppearanceNameAqua,
45 | },
46 | Bind: []interface{}{app, repo},
47 | })
48 |
49 | if err != nil {
50 | println("Error:", err.Error())
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/repository/branch.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "git-helper/utils"
7 | "strings"
8 | )
9 |
10 | type Branch struct {
11 | Hash string `json:"hash"`
12 | Name string `json:"name"`
13 | RefName string `json:"refName"`
14 | Upstream string `json:"upstream"`
15 | }
16 |
17 | func (r *Repository) PushBranch(name string) (string, error) {
18 | //git push -u origin main
19 | out, err := utils.RunCmdByPath(r.Path, "git", "push", "-u", "origin", name)
20 | if err != nil {
21 | return "", err
22 | }
23 |
24 | return out, nil
25 | }
26 |
27 | func (r *Repository) getBranch(isAll bool) ([]Branch, error) {
28 |
29 | var branch []Branch
30 |
31 | f := fmt.Sprintf("--format=%s", `{"hash":"%(objectname)","name":"%(refname:strip=2)","refName":"%(refname)","upstream":"%(upstream)"},`)
32 | var out string
33 | var err error
34 | if isAll {
35 | out, err = utils.RunCmdByPath(r.Path, "git", "branch", f, "-a", "--sort=-committerdate")
36 | } else {
37 | out, err = utils.RunCmdByPath(r.Path, "git", "branch", f, "--sort=-committerdate")
38 | }
39 | if err != nil {
40 | return nil, err
41 | }
42 | jsonStr := fmt.Sprintf("[%s]", strings.TrimRight(strings.TrimSpace(out), ","))
43 |
44 | err = json.Unmarshal([]byte(jsonStr), &branch)
45 | if err != nil {
46 | return nil, err
47 | }
48 |
49 | return branch, nil
50 | }
51 |
52 | func (r *Repository) GetLocalBranch() ([]Branch, error) { //menu
53 | return r.getBranch(false)
54 | }
55 | func (r *Repository) GetAllBranch() ([]Branch, error) { //use branch manage
56 | return r.getBranch(true)
57 | }
58 | func (r *Repository) SwitchBranch(branchName string) (bool, error) {
59 | _, err := utils.RunCmdByPath(r.Path, "git", "checkout", branchName)
60 | if err != nil {
61 | return false, err
62 | }
63 | return true, nil
64 | }
65 | func (r *Repository) AddBranch(branchName string) error {
66 | _, err := utils.RunCmdByPath(r.Path, "git", "branch", branchName)
67 | if err != nil {
68 | return err
69 | }
70 | return nil
71 | }
72 | func (r *Repository) DelBranch(branchName string, delRemote bool) (string, error) {
73 |
74 | if delRemote {
75 | out, err := utils.RunCmdByPath(r.Path, "git", "push", "origin", "-d", branchName)
76 | if err != nil {
77 | return out, err
78 | }
79 | }
80 |
81 | out, err := utils.RunCmdByPath(r.Path, "git", "branch", "-d", branchName)
82 | if err != nil {
83 | return out, err
84 | }
85 |
86 | return out, nil
87 | }
88 |
89 | func (r *Repository) GetBranchHash(branchName string) (string, error) {
90 |
91 | hash, err := utils.RunCmdByPath(r.Path, "git", "rev-parse", branchName)
92 | if err != nil {
93 | return "", err
94 | }
95 | h := strings.TrimSpace(hash)
96 | return h, nil
97 | }
98 |
99 | func (r *Repository) GetCurrentBranch() (string, error) {
100 | branch, err := utils.RunCmdByPath(r.Path, "git", "rev-parse", "--abbrev-ref", "HEAD")
101 | if err != nil {
102 | return "", err
103 | }
104 | b := strings.TrimSpace(branch)
105 | if strings.HasPrefix(b, "heads/") {
106 | b = strings.TrimLeft(b, "heads/")
107 | }
108 | return b, nil
109 | }
110 |
--------------------------------------------------------------------------------
/repository/commit.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "git-helper/utils"
7 | "strings"
8 | )
9 |
10 | type Signature struct {
11 | Name string `json:"name"`
12 | Email string `json:"email"`
13 | When string `json:"when"`
14 | }
15 |
16 | type Commit struct {
17 | Hash string `json:"hash"`
18 | Author string `json:"author"`
19 | Committer Signature `json:"committer"`
20 | Message string `json:"message"`
21 | TreeHash string `json:"treeHash"`
22 | ParentHashes string `json:"parentHashes"`
23 | IsRemoteSync bool `json:"isRemoteSync"`
24 | }
25 |
26 | func (r *Repository) Commits(branch string) ([]Commit, error) {
27 | if branch == "" {
28 | return nil, errors.New("branch name error")
29 | }
30 |
31 | var logs []Commit
32 | //f := fmt.Sprintf("--format=format:%s", `{"hash":"%H","author":"%an","message":"%s","treeHash":"%T","parentHashes":"%P","committer":{"name":"%cn","email":"%ce","when":"%cr"}},`)
33 | f := fmt.Sprintf("--format=format:%s", `%H<||>%an<||>%s<||>%T<||>%P<||>%cn<||>%ce<||>%cr<|n|>`)
34 | out, err := utils.RunCmdByPath(r.Path, "git", "log", branch, f, "-n 60")
35 | if err != nil {
36 | return nil, err
37 | }
38 | isRemoteRepo, err := r.IsRemoteRepo()
39 | if err != nil {
40 | return nil, err
41 | }
42 | var noRemoteSyncHash string
43 | if isRemoteRepo {
44 | //TODO是否有这个远程分支
45 | arg := fmt.Sprintf("origin/%s...%s", branch, branch)
46 | noRemoteSyncHash, err = utils.RunCmdByPath(r.Path, "git", "log", `--pretty=format:"%H"`, arg)
47 | if err != nil {
48 | return nil, err
49 | }
50 | }
51 | outs := strings.Split(out, "<|n|>")
52 | for _, r := range outs {
53 | rows := strings.Split(strings.TrimSpace(r), "<||>")
54 | if len(rows) != 8 {
55 | continue
56 | }
57 | //`{"hash":"%H","author":"%an","message":"%s","treeHash":"%T","parentHashes":"%P","committer":{"name":"%cn","email":"%ce","when":"%cr"}},`
58 | hash := strings.TrimSpace(rows[0])
59 |
60 | var isRemoteSync bool
61 | if isRemoteRepo {
62 | isRemoteSync = !strings.Contains(noRemoteSyncHash, hash)
63 | }
64 |
65 | logs = append(logs, Commit{
66 | Hash: hash,
67 | Author: rows[1],
68 | Message: rows[2],
69 | TreeHash: rows[3],
70 | ParentHashes: rows[4],
71 | Committer: Signature{
72 | Name: rows[5],
73 | Email: rows[6],
74 | When: rows[7],
75 | },
76 | IsRemoteSync: isRemoteSync,
77 | })
78 | }
79 |
80 | return logs, nil
81 | }
82 |
83 | type LogDesc struct {
84 | Tag []string `json:"tag"`
85 | Branch []string `json:"branch"`
86 | }
87 |
88 | type Log struct {
89 | Hash string `json:"hash"`
90 | ParentHashes []string `json:"parentHashes"`
91 | Desc LogDesc `json:"desc"`
92 | Author string `json:"author"`
93 | Message string `json:"message"`
94 | }
95 |
96 | func (r *Repository) CommitsLog() ([]Log, error) {
97 | //git log --all --date-order --pretty="%h<|>%p<|>%d<|>%an<|>%s<|n|>"
98 |
99 | f := fmt.Sprintf("--pretty=%s", `%h<||>%p<||>%d<||>%an<||>%s<|n|>`)
100 |
101 | out, err := utils.RunCmdByPath(r.Path, "git", "log", "--all", "--date-order", "-n 100", f)
102 | if err != nil {
103 | return nil, err
104 | }
105 |
106 | outs := strings.Split(out, "<|n|>")
107 | var commitIds []string
108 | var logs []Log
109 | for _, log := range outs {
110 | rows := strings.Split(strings.TrimSpace(log), "<||>")
111 | if len(rows) != 5 {
112 | continue
113 | }
114 | commitIds = append(commitIds, rows[0])
115 | logs = append(logs, Log{
116 | Hash: rows[0],
117 | ParentHashes: strings.Split(strings.TrimSpace(rows[1]), " "),
118 | Desc: parseDesc(rows[2]),
119 | Author: rows[3],
120 | Message: rows[4],
121 | })
122 | }
123 |
124 | commitIdsStr := strings.Join(commitIds, "")
125 | for i, log := range logs {
126 | var ph []string
127 | for _, p := range log.ParentHashes {
128 | //如果父元素等于空字符表示它没有父元素,是提交的起点
129 | //因为只取了100条日志,如果父元素没在本次的记录里面,也当为空
130 | if p == "" || !strings.Contains(commitIdsStr, p) {
131 | ph = append(ph, "")
132 | } else {
133 | ph = append(ph, p)
134 | }
135 | }
136 | logs[i].ParentHashes = ph
137 | }
138 |
139 | return logs, nil
140 | }
141 |
142 | func parseDesc(d string) LogDesc {
143 | s := strings.TrimSpace(d)
144 | r := strings.TrimRight(s, ")")
145 | str := strings.TrimLeft(r, "(")
146 | outs := strings.Split(str, ",")
147 | var desc LogDesc
148 | for _, f := range outs {
149 | flag := strings.TrimSpace(f)
150 | if strings.HasPrefix(flag, "tag:") {
151 | tag := strings.TrimLeft(flag, "tag:")
152 | desc.Tag = append(desc.Tag, strings.TrimSpace(tag))
153 | } else if flag != "" {
154 | desc.Branch = append(desc.Branch, flag)
155 | }
156 | }
157 | return desc
158 | }
159 |
--------------------------------------------------------------------------------
/repository/diffCommit.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | "git-helper/utils"
5 | "strings"
6 | )
7 |
8 | type ChangesFile struct {
9 | FileName string `json:"fileName"`
10 | Index int `json:"index"`
11 | Desc string `json:"desc"`
12 | }
13 | type DiffCommitInfo struct {
14 | ChangesFiles []ChangesFile `json:"changesFiles"`
15 | Statistics string `json:"statistics"`
16 | }
17 |
18 | func (r *Repository) DiffCommit(newCommitId, oldCommitId string) (DiffCommitInfo, error) {
19 |
20 | var content DiffCommitInfo
21 |
22 | out, err := utils.RunCmdByPath(r.Path, "git", "diff", oldCommitId, newCommitId, "--stat")
23 | if err != nil {
24 | return content, err
25 | }
26 |
27 | outs := strings.Split(strings.Trim(out, "\n"), "\n")
28 |
29 | content.Statistics = outs[len(outs)-1]
30 |
31 | var changes []ChangesFile
32 |
33 | for i, s := range outs[0 : len(outs)-1] {
34 | sa := strings.Split(s, "|")
35 | if len(sa) != 2 {
36 | changes = append(changes, ChangesFile{
37 | FileName: s,
38 | Index: i,
39 | Desc: "",
40 | })
41 | } else {
42 | changes = append(changes, ChangesFile{
43 | FileName: strings.Trim(sa[0], " "),
44 | Index: i,
45 | Desc: strings.Trim(sa[1], " "),
46 | })
47 | }
48 |
49 | }
50 | content.ChangesFiles = changes
51 | return content, nil
52 | }
53 |
--------------------------------------------------------------------------------
/repository/diffWork.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | "git-helper/utils"
5 | "strings"
6 | )
7 |
8 | type DiffFlag = uint
9 |
10 | const (
11 | DEFAULT DiffFlag = iota
12 | ADDED
13 | REMOVED
14 | )
15 |
16 | type DiffContent struct {
17 | Content string `json:"content"`
18 | Type DiffFlag `json:"type"`
19 | Index int `json:"index"`
20 | }
21 |
22 | func (r *Repository) DiffWorkStage(filePath string) ([]DiffContent, error) {
23 |
24 | var content []DiffContent
25 |
26 | out, err := utils.RunCmdByPath(r.Path, "git", "diff", filePath)
27 | if err != nil {
28 | return content, err
29 | }
30 | outs := strings.Split(out, "\n")
31 |
32 | for i, s := range outs {
33 | f := getDiffFlag(s)
34 | content = append(content, DiffContent{
35 | Index: i,
36 | Content: s,
37 | Type: f,
38 | })
39 | }
40 |
41 | return content, nil
42 | }
43 |
44 | func (r *Repository) ShowWorkTreeFile(filePath string, flag DiffFlag) ([]DiffContent, error) {
45 |
46 | var content []DiffContent
47 |
48 | out, err := utils.RunCmdByPath(r.Path, "git", "show", ":"+filePath)
49 | if err != nil {
50 | return content, err
51 | }
52 | outs := strings.Split(out, "\n")
53 |
54 | for i, s := range outs {
55 | content = append(content, DiffContent{
56 | Index: i,
57 | Content: s,
58 | Type: flag,
59 | })
60 | }
61 |
62 | return content, nil
63 | }
64 |
65 | func getDiffFlag(c string) DiffFlag {
66 | if strings.HasPrefix(c, "+") {
67 | return ADDED
68 | }
69 | if strings.HasPrefix(c, "-") {
70 | return REMOVED
71 | }
72 | return DEFAULT
73 | }
74 |
--------------------------------------------------------------------------------
/repository/merge.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | "errors"
5 | "git-helper/utils"
6 | "regexp"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | type MergeKind int
12 |
13 | const conflictRegexp = `\+[<>=]{7}\s\.our[\w\W]*?\+[<>=]{7}\s\.their`
14 |
15 | const (
16 | Invalid MergeKind = iota
17 | Conflicted
18 | Clean
19 | )
20 |
21 | type MergeResult struct {
22 | Kind MergeKind `json:"kind"`
23 | Count int `json:"count"`
24 | }
25 |
26 | func (r *Repository) PreMergeResult(currentHash, MergeHash string) (MergeResult, error) {
27 |
28 | var invalidResult = MergeResult{
29 | Kind: Invalid,
30 | Count: 0,
31 | }
32 |
33 | h, err := utils.RunCmdByPath(r.Path, "git", "merge-base", currentHash, MergeHash)
34 | if err != nil {
35 | return invalidResult, err
36 | }
37 | baseHash := strings.TrimSpace(h)
38 | out, err := utils.RunCmdByPath(r.Path, "git", "merge-tree", baseHash, currentHash, MergeHash)
39 | if err != nil {
40 | return invalidResult, err
41 | }
42 |
43 | reg := regexp.MustCompile(conflictRegexp)
44 | matches := reg.FindAllString(out, -1)
45 |
46 | if len(matches) != 0 {
47 | return MergeResult{Kind: Conflicted, Count: len(matches)}, nil
48 | }
49 | out, err = utils.RunCmdByPath(r.Path, "git", "rev-list", "--left-right", "--count", currentHash+"..."+MergeHash)
50 | if err != nil {
51 | return invalidResult, err
52 | }
53 |
54 | c := strings.Split(out, "\t")
55 |
56 | if len(c) != 2 {
57 | return invalidResult, errors.New("rev-list out err")
58 | }
59 | i, err := strconv.ParseInt(strings.TrimSpace(c[1]), 10, 64)
60 | if err != nil {
61 | return invalidResult, err
62 | }
63 | return MergeResult{Kind: Clean, Count: int(i)}, nil
64 | }
65 |
66 | func (r *Repository) MergeRebase(ourBranch, theirBranch string) (string, error) {
67 |
68 | ok, err := r.SwitchBranch(ourBranch)
69 | if err != nil {
70 | return "", err
71 | }
72 | if !ok {
73 | return "", errors.New("switch branch error")
74 | }
75 | out, err := utils.RunCmdByPath(r.Path, "git", "rebase", theirBranch)
76 |
77 | if err != nil {
78 | return "", err
79 | }
80 | return out, nil
81 | }
82 |
83 | func (r *Repository) MergeCommit(ourBranch, theirBranch string) (string, error) {
84 | ok, err := r.SwitchBranch(ourBranch)
85 | if err != nil {
86 | return "", err
87 | }
88 | if !ok {
89 | return "", errors.New("switch branch error")
90 | }
91 | out, err := utils.RunCmdByPath(r.Path, "git", "merge", theirBranch)
92 |
93 | if err != nil {
94 | return "", err
95 | }
96 | return out, nil
97 | }
98 |
99 | func (r *Repository) MergeSquash(ourBranch, theirBranch string) (string, error) {
100 | ok, err := r.SwitchBranch(ourBranch)
101 | if err != nil {
102 | return "", err
103 | }
104 | if !ok {
105 | return "", errors.New("switch branch error")
106 | }
107 | out, err := utils.RunCmdByPath(r.Path, "git", "merge", "--squash", theirBranch)
108 |
109 | if err != nil {
110 | return "", err
111 | }
112 | return out, nil
113 | }
114 |
--------------------------------------------------------------------------------
/repository/mian.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | "git-helper/utils"
5 | "strings"
6 | )
7 |
8 | type Repository struct {
9 | Path string
10 | }
11 |
12 | func New() *Repository {
13 | return &Repository{}
14 | }
15 |
16 | func (r *Repository) SwitchRepository(path string) error {
17 | r.Path = path
18 | return nil
19 | }
20 |
21 | func (r *Repository) GitPull() (string, error) {
22 | return utils.RunCmdByPath(r.Path, "git", "pull")
23 | }
24 | func (r *Repository) GitPush() (string, error) {
25 | return utils.RunCmdByPath(r.Path, "git", "push")
26 | }
27 |
28 | func (r *Repository) GitRemoteUrl(path string) (string, error) {
29 | url, err := utils.RunCmdByPath(path, "git", "remote", "get-url", "origin")
30 | if err != nil {
31 | return "", err
32 | }
33 | return strings.TrimSpace(url), nil
34 | }
35 |
--------------------------------------------------------------------------------
/repository/tag.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | "fmt"
5 | "git-helper/utils"
6 | "regexp"
7 | "strings"
8 | )
9 |
10 | type Tag struct {
11 | Name string `json:"name"`
12 | RefName string `json:"refName"`
13 | Type string `json:"type"`
14 | Message string `json:"message"`
15 | Hash string `json:"hash"`
16 | Time string `json:"time"`
17 | }
18 |
19 | func (r *Repository) Tags() ([]Tag, error) {
20 | var tags []Tag
21 | f := fmt.Sprintf("--format=%s", `%(refname:strip=2)<||>%(refname)<||>%(objecttype)<||>%(subject)<||>%(objectname)<||>%(creatordate:relative)<|n|>`)
22 |
23 | out, err := utils.RunCmdByPath(r.Path, "git", "tag", "--sort=-creatordate", f)
24 | if err != nil {
25 | return nil, err
26 | }
27 | outs := strings.Split(out, "<|n|>")
28 |
29 | for _, tag := range outs {
30 | rows := strings.Split(strings.TrimSpace(tag), "<||>")
31 | if len(rows) != 6 {
32 | continue
33 | }
34 | tags = append(tags, Tag{
35 | Name: rows[0],
36 | RefName: rows[1],
37 | Type: rows[2],
38 | Message: rows[3],
39 | Hash: rows[4],
40 | Time: rows[5],
41 | })
42 | }
43 | return tags, nil
44 | //下面这种如果用户标签信息中含有"会导致失败
45 | //var tags []Tag
46 | //f := fmt.Sprintf("--format=%s", `{"name":"%(refname:short)","refName":"%(refname)","type":"%(objecttype)","message":"%(subject)","hash":"%(objectname)","time":"%(creatordate:relative)"},`)
47 | //
48 | //out, err := utils.RunCmdByPath(r.Path, "git", "tag", f)
49 | //if err != nil {
50 | // return nil, err
51 | //}
52 | //jsonStr := fmt.Sprintf("[%s]", strings.TrimRight(strings.TrimSpace(out), ","))
53 | //fmt.Println(jsonStr)
54 | //err = json.Unmarshal([]byte(jsonStr), &tags)
55 | //if err != nil {
56 | // return nil, err
57 | //}
58 | //
59 | //return tags, nil
60 | }
61 |
62 | func (r *Repository) DelTag(tagName string, delRemote bool) (string, error) {
63 |
64 | if delRemote {
65 | out, err := utils.RunCmdByPath(r.Path, "git", "push", "origin", "--delete", "tag", tagName)
66 | if err != nil {
67 | return out, err
68 | }
69 | }
70 |
71 | out, err := utils.RunCmdByPath(r.Path, "git", "tag", "-d", tagName)
72 | if err != nil {
73 | return out, err
74 | }
75 |
76 | return out, nil
77 | }
78 |
79 | //git tag v10.0 -m "version 1.0"
80 |
81 | func (r *Repository) CreateTag(tag string, msg string) (string, error) {
82 | var out string
83 | var err error
84 | if msg == "" {
85 | out, err = utils.RunCmdByPath(r.Path, "git", "tag", tag)
86 | } else {
87 | out, err = utils.RunCmdByPath(r.Path, "git", "tag", tag, "-m", msg)
88 | }
89 | if err != nil {
90 | return out, err
91 | }
92 | return out, nil
93 | }
94 |
95 | func (r *Repository) RemoteTags() ([]string, error) {
96 | var tags []string
97 | isRemoteRepo, err := r.IsRemoteRepo()
98 | if err != nil || !isRemoteRepo {
99 | return tags, nil
100 | }
101 | out, err := utils.RunCmdByPath(r.Path, "git", "ls-remote", "--tags", "--refs", "origin")
102 | if err != nil {
103 | return tags, err
104 | }
105 | re := regexp.MustCompile(`refs/tags/(.+)\b`)
106 | matches := re.FindAllStringSubmatch(out, -1)
107 | for _, match := range matches {
108 | tags = append(tags, match[1])
109 | }
110 | return tags, nil
111 | }
112 |
113 | //git push origin v1.0
114 |
115 | func (r *Repository) PushTag(name string) (string, error) {
116 |
117 | out, err := utils.RunCmdByPath(r.Path, "git", "push", "origin", name)
118 | if err != nil {
119 | return "", err
120 | }
121 |
122 | return out, nil
123 | }
124 |
125 | func (r *Repository) CreateTagByCommitId(tag string, commitId string) (string, error) {
126 | //git tag v1.0 a867b4af
127 | out, err := utils.RunCmdByPath(r.Path, "git", "tag", tag, commitId)
128 | if err != nil {
129 | return out, err
130 | }
131 | return out, nil
132 | }
133 |
--------------------------------------------------------------------------------
/repository/utils.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | "errors"
5 | "git-helper/utils"
6 | "os/exec"
7 | sysRuntime "runtime"
8 | "strings"
9 | )
10 |
11 | func (r *Repository) OpenTerminal() error {
12 |
13 | var cmd *exec.Cmd
14 | switch sysRuntime.GOOS {
15 | case "darwin":
16 | cmd = exec.Command("open", "-b", "com.apple.Terminal", r.Path)
17 | case "linux":
18 | cmd = exec.Command("x-terminal-emulator", "-e", "cd "+r.Path+";bash")
19 | case "windows":
20 | cmd = exec.Command("start", "cmd")
21 | default:
22 | return errors.New("unsupported operating system")
23 | }
24 | err := cmd.Run()
25 | if err != nil {
26 | return err
27 | }
28 | return nil
29 | }
30 |
31 | func (r *Repository) OpenFileManage() error {
32 | var cmd *exec.Cmd
33 | switch sysRuntime.GOOS {
34 | case "darwin":
35 | cmd = exec.Command("open", r.Path)
36 | case "linux":
37 | cmd = exec.Command("xdg-open", r.Path)
38 | case "windows":
39 | cmd = exec.Command(`cmd`, `/c`, `explorer`, r.Path)
40 | default:
41 | return errors.New("unsupported operating system")
42 | }
43 | err := cmd.Start()
44 | if err != nil {
45 | return err
46 | }
47 | return nil
48 | }
49 |
50 | func (r *Repository) RunCmdInRepository(cmd string, arg []string) (string, error) {
51 | return utils.RunCmdByPath(r.Path, cmd, arg...)
52 | }
53 | func (r *Repository) IsRemoteRepo() (bool, error) {
54 |
55 | out, err := utils.RunCmdByPath(r.Path, "git", "remote", "-v")
56 | if err != nil {
57 | return false, err
58 | }
59 |
60 | if strings.TrimSpace(out) == "" {
61 | return false, nil
62 | }
63 | return true, nil
64 | }
65 |
--------------------------------------------------------------------------------
/repository/workTree.go:
--------------------------------------------------------------------------------
1 | package repository
2 |
3 | import (
4 | "errors"
5 | "git-helper/utils"
6 | "strings"
7 | )
8 |
9 | type FileStatus struct {
10 | Name string `json:"name"`
11 | Path string `json:"path"`
12 | Staging string `json:"staging"`
13 | Worktree string `json:"worktree"`
14 | }
15 |
16 | func (r *Repository) FileStatus() ([]FileStatus, error) {
17 | out, err := utils.RunCmdByPath(r.Path, "git", "--no-optional-locks", "status", "--porcelain")
18 |
19 | if err != nil {
20 | return nil, err
21 | }
22 |
23 | var changes []FileStatus
24 | files := strings.Split(strings.Trim(out, "\n"), "\n")
25 | if files == nil {
26 | return changes, nil
27 | }
28 | for _, c := range files {
29 | if len(c) < 4 {
30 | continue
31 | }
32 | staging := c[0:1]
33 | worktree := c[1:2]
34 | path := c[3:]
35 |
36 | if staging == "R" {
37 | p := strings.Split(path, "->")
38 | if len(p) != 2 {
39 | return changes, errors.New("staging r error")
40 | }
41 | path = strings.TrimSpace(p[1])
42 | }
43 |
44 | changes = append(changes, FileStatus{
45 | Name: c[3:],
46 | Path: path,
47 | Staging: staging,
48 | Worktree: worktree,
49 | })
50 | }
51 | return changes, nil
52 | }
53 |
54 | func (r *Repository) Commit(title, msg string, fileList []string) (string, error) {
55 | for _, f := range fileList {
56 | out, err := utils.RunCmdByPath(r.Path, "git", "add", f)
57 | if err != nil {
58 | return out, err
59 | }
60 | }
61 | var message = title
62 | if msg != "" {
63 | message = message + ":" + msg
64 | }
65 | out, err := utils.RunCmdByPath(r.Path, "git", "commit", "-m", message)
66 |
67 | if err != nil {
68 | return out, err
69 | }
70 | return out, nil
71 |
72 | }
73 |
74 | func (r *Repository) DiscardChanges(filePath string) (string, error) {
75 |
76 | out, err := utils.RunCmdByPath(r.Path, "git", "checkout", "HEAD", "--", filePath)
77 |
78 | if err != nil {
79 | return "", err
80 | }
81 | return out, nil
82 | }
83 |
--------------------------------------------------------------------------------
/screenshots/branch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xusenlin/git-helper/19e88fa5f00ec16a87de17963883de7ddfc176dc/screenshots/branch.png
--------------------------------------------------------------------------------
/screenshots/change.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xusenlin/git-helper/19e88fa5f00ec16a87de17963883de7ddfc176dc/screenshots/change.png
--------------------------------------------------------------------------------
/screenshots/history.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xusenlin/git-helper/19e88fa5f00ec16a87de17963883de7ddfc176dc/screenshots/history.png
--------------------------------------------------------------------------------
/screenshots/tag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xusenlin/git-helper/19e88fa5f00ec16a87de17963883de7ddfc176dc/screenshots/tag.png
--------------------------------------------------------------------------------
/utils/common.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "crypto/sha256"
5 | "errors"
6 | "fmt"
7 | "os"
8 | "os/exec"
9 | )
10 |
11 | func IsDir(path string) bool {
12 |
13 | if info, err := os.Stat(path); err == nil {
14 | return info.IsDir()
15 | }
16 | return false
17 | }
18 |
19 | func RunCmdByPath(path string, cmdName string, arg ...string) (string, error) {
20 | if !IsDir(path) {
21 | return "", errors.New("Can't find repository path:" + path)
22 | }
23 |
24 | cmd := exec.Command(cmdName, arg...)
25 | cmd.Dir = path
26 |
27 | out, err := cmd.CombinedOutput()
28 | if err != nil {
29 | return "", errors.New(string(out))
30 | }
31 | return string(out), nil
32 | }
33 |
34 | func Sha256(s string) string {
35 | h := sha256.Sum256([]byte(s))
36 | hashString := fmt.Sprintf("%x", h)
37 | return hashString
38 | }
39 |
40 | func FileIsExisted(filename string) bool {
41 | existed := true
42 | if _, err := os.Stat(filename); os.IsNotExist(err) {
43 | existed = false
44 | }
45 | return existed
46 | }
47 |
48 | func RemoveFile(file string) error {
49 | if FileIsExisted(file) {
50 | return os.Remove(file)
51 | }
52 | return nil
53 | }
54 |
--------------------------------------------------------------------------------
/wails.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://wails.io/schemas/config.v2.json",
3 | "name": "GitHelper",
4 | "outputfilename": "GitHelper",
5 | "frontend:install": "npm install",
6 | "frontend:build": "npm run build",
7 | "frontend:dev:watcher": "npm run dev",
8 | "frontend:dev:serverUrl": "auto",
9 | "author": {
10 | "name": "五木老祖",
11 | "email": "wumulaozu@gmail.com"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------