From c2d4fad4e103125616eab0dd85f20d0658d70417 Mon Sep 17 00:00:00 2001 From: tiddlygit-test Date: Mon, 3 May 2021 01:58:46 +0800 Subject: [PATCH] refactor: things into git-sync-js --- localization/locales/en/translation.json | 7 +- localization/locales/zh_CN/translation.json | 23 +- package-lock.json | 141 +++++++- package.json | 1 + src/services/git/github.ts | 30 -- src/services/git/index.ts | 375 ++++++++++---------- src/services/git/inspect.ts | 38 -- src/services/git/interface.ts | 10 +- src/services/git/sync.ts | 199 ----------- 9 files changed, 330 insertions(+), 494 deletions(-) delete mode 100644 src/services/git/github.ts delete mode 100644 src/services/git/inspect.ts delete mode 100644 src/services/git/sync.ts diff --git a/localization/locales/en/translation.json b/localization/locales/en/translation.json index ca06ba84..561ce751 100644 --- a/localization/locales/en/translation.json +++ b/localization/locales/en/translation.json @@ -166,7 +166,12 @@ "SynchronizationFinish": "Synchronization complete", "UsingUrlAndUsername": "Using Git Url {{githubRepoUrl}} with username {{username}} and accessToken {{accessToken}}", "GitTokenMissing": "Git token missing", - "MainWindowMissing": "This program can't access main window data, can't run normally." + "MainWindowMissing": "This program can't access main window data, can't run normally.", + "AddingFiles": "Start Git Add your files that needs backed up", + "AddComplete": "Git Add successful", + "CheckingLocalGitRepoSanity": "Checking whether the local Git repository is properly initialized", + "CheckingLocalSyncState": "Detecting whether the local state needs to be synchronized to the cloud", + "CheckingRebaseStatus": "Analyzing the rebase processing plan" }, "Cancel": "Cancel", "Preference": { diff --git a/localization/locales/zh_CN/translation.json b/localization/locales/zh_CN/translation.json index d496a964..63db6f81 100644 --- a/localization/locales/zh_CN/translation.json +++ b/localization/locales/zh_CN/translation.json @@ -122,22 +122,24 @@ "UserName": "编辑者名" }, "Log": { - "StartGitInitialization": "开始初始化本地Git仓库", - "StartConfiguringGithubRemoteRepository": "仓库初始化完毕,开始配置Github远端仓库", - "StartBackupToGithubRemote": "正在将Wiki所在的本地Git备份到Github远端仓库,需要的时间取决于网速,请耐心等待", - "GitRepositoryConfigurateFailed": "Git仓库配置失败,详见错误日志", - "GitRepositoryConfigurationFinished": "Git仓库配置完毕", + "StartGitInitialization": "开始初始化本地 Git 仓库", + "StartConfiguringGithubRemoteRepository": "仓库初始化完毕,开始配置 Git 云端仓库", + "StartBackupToGithubRemote": "正在将 Wiki 所在的本地 Git 备份到 Github 云端仓库,需要的时间取决于网速,请耐心等待", + "GitRepositoryConfigurateFailed": "Git 仓库配置失败,详见错误日志", + "GitRepositoryConfigurationFinished": "Git 仓库配置完毕", "SynchronizationFailed": "同步失败!你需要用 Github Desktop 等工具检查当前 Git 仓库的状态。失败可能是网络原因导致的,如果的确如此,可在调整网络后重试。", - "NotAGitRepository": "不是一个 git 仓库", + "NotAGitRepository": "不是一个 Git 仓库", "CantSynchronizeAndSyncScriptIsInDeadLoop": "无法同步,而且同步脚本陷入死循环", "CantSyncInSpecialGitStateAutoFixFailed": "无法同步,这个文件夹处在特殊状态,不能直接进行同步,已尝试自动修复,但还是出现错误,请先解决所有冲突(例如使用 VSCode 打开),如果还不行,请尝试用专业 Git 工具(Source Tree, GitKraken)解决问题", "CantSyncInSpecialGitStateAutoFixSucceed": "这个文件夹处在特殊状态,本来不能直接进行同步,但已自动修复", "PrepareSync": "准备同步,使用登录的作者信息", - "CommitComplete": "本地提交(Commit)完成", + "CommitComplete": "本地提交(Commit)完成", "CantSyncGitNotInitialized": "无法同步,这个文件夹没有初始化为 Git 仓库", - "HaveThingsToCommit": "有需要提交(commit)的内容,正在自动提交", + "HaveThingsToCommit": "有需要提交(Commit)的内容,正在自动提交", "PreparingUserInfo": "正在配置身份信息", "GitTokenMissing": "Git 身份信息缺失", + "AddingFiles": "开始添加(Git Add)待备份的文件", + "AddComplete": "添加(Git Add)成功", "MainWindowMissing": "程序无法获取主窗口信息,无法正常运行。", "FetchingData": "正在拉取云端数据,以便进行数据比对", "NoNeedToSync": "无需同步,本地状态和云端一致", @@ -145,13 +147,16 @@ "GitPushFailed": "Git上传的结果不佳,这通常意味着有网络问题", "LocalStateBehindSync": "本地状态落后于云端,开始合并云端数据", "GitMergeFailed": "Git合并的结果不佳,可能合并策略有漏洞", + "CheckingLocalGitRepoSanity": "正在检测本地 Git 仓库是否正确地初始化了", + "CheckingLocalSyncState": "正在检测本地状态是否需要同步到云端", "LocalStateDivergeRebase": "本地状态与云端有分歧,开始变基(Rebase)", "RebaseSucceed": "变基(Rebase)成功,开始上传", + "CheckingRebaseStatus": "正在分析变基(Rebase)的处理方案", "RebaseConflictNeedsResolve": "变基(Rebase)时发现冲突,需要解决冲突", "SyncFailedSystemError": "同步失败,同步系统可能出现问题", "PerformLastCheckBeforeSynchronizationFinish": "进行同步结束前最后的检查", "SynchronizationFinish": "同步完成", - "PrepareCloneOnlineWiki": "准备克隆线上Wiki", + "PrepareCloneOnlineWiki": "准备克隆线上 Wiki", "StartFetchingFromGithubRemote": "正在拉取Github远端仓库的数据,需要的时间取决于网速和仓库大小,请耐心等待", "UsingUrlAndUsername": "使用 Git Url {{githubRepoUrl}} 和用户名 {{username}} 和 accessToken {{accessToken}}" }, diff --git a/package-lock.json b/package-lock.json index 0f7a2436..4224ed83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2872,7 +2872,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, "requires": { "defer-to-connect": "^1.0.1" } @@ -5545,6 +5544,14 @@ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, + "checksum": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/checksum/-/checksum-0.1.1.tgz", + "integrity": "sha1-3GUn1MkL6FYNvR7Uzs8yl9Uo6ek=", + "requires": { + "optimist": "~0.3.5" + } + }, "cheerio": { "version": "1.0.0-rc.6", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.6.tgz", @@ -5708,8 +5715,7 @@ "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "chrome-launcher": { "version": "0.13.4", @@ -7237,8 +7243,7 @@ "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" }, "define-properties": { "version": "1.1.3", @@ -7541,6 +7546,96 @@ "pify": "^4.0.1" } }, + "dugite": { + "version": "1.103.0", + "resolved": "https://registry.npmjs.org/dugite/-/dugite-1.103.0.tgz", + "integrity": "sha512-8rKO/jQX2HKfSd5wNG/l3HnUfQPKqyC3+D+3CR5Go4+BJOyCPScQwiAVW+eeKLqHFOvjq/w67+ymMyPGxUqhIA==", + "requires": { + "checksum": "^0.1.1", + "got": "^9.6.0", + "mkdirp": "^0.5.1", + "progress": "^2.0.3", + "rimraf": "^2.5.4", + "tar": "^4.4.7" + }, + "dependencies": { + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + } + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, "duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -10379,7 +10474,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dev": true, "requires": { "minipass": "^2.6.0" } @@ -10705,6 +10799,16 @@ "omggif": "^1.0.10" } }, + "git-sync-js": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/git-sync-js/-/git-sync-js-0.1.4.tgz", + "integrity": "sha512-zsiBtIkdLk3papSKq9/A8BeuwL0UyESSA4YHyVEsQtlSUiIukfk7O3mdqL4PgjrKDqPd+Ec0x3xMFZkr4Z1GPw==", + "requires": { + "dugite": "^1.103.0", + "fs-extra": "^9.1.0", + "lodash": "^4.17.21" + } + }, "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -13678,7 +13782,6 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -13766,7 +13869,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dev": true, "requires": { "minipass": "^2.9.0" } @@ -14838,6 +14940,14 @@ } } }, + "optimist": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", + "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", + "requires": { + "wordwrap": "~0.0.2" + } + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -15737,8 +15847,7 @@ "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" }, "progress-stream": { "version": "1.2.0", @@ -18197,7 +18306,6 @@ "version": "4.4.13", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "dev": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", @@ -18530,8 +18638,7 @@ "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" }, "to-regex": { "version": "3.0.2", @@ -20190,6 +20297,11 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", @@ -20322,8 +20434,7 @@ "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yaml": { "version": "1.10.2", diff --git a/package.json b/package.json index 1ecc2706..79228c09 100755 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "electron-window-state": "5.0.3", "errio": "1.2.2", "fs-extra": "9.1.0", + "git-sync-js": "^0.1.4", "i18next": "20.2.2", "i18next-electron-fs-backend": "1.3.6", "i18next-fs-backend": "1.1.1", diff --git a/src/services/git/github.ts b/src/services/git/github.ts deleted file mode 100644 index ae2f1524..00000000 --- a/src/services/git/github.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { trim } from 'lodash'; -import { GitProcess } from 'dugite'; -import { getRemoteUrl } from './inspect'; - -const getGitUrlWithCredential = (rawUrl: string, username: string, accessToken: string): string => - trim(`${rawUrl}.git`.replace(/\n/g, '').replace('https://github.com/', `https://${username}:${accessToken}@github.com/`)); -const getGitUrlWithOutCredential = (urlWithCredential: string): string => trim(urlWithCredential.replace(/.+@/, 'https://')); - -/** - * Add remote with credential - * @param {string} wikiFolderPath - * @param {string} githubRepoUrl - * @param {{ login: string, email: string, accessToken: string }} userInfo - */ -export async function credentialOn(wikiFolderPath: string, githubRepoUrl: string, userName: string, accessToken: string): Promise { - const gitUrlWithCredential = getGitUrlWithCredential(githubRepoUrl, userName, accessToken); - await GitProcess.exec(['remote', 'add', 'origin', gitUrlWithCredential], wikiFolderPath); - await GitProcess.exec(['remote', 'set-url', 'origin', gitUrlWithCredential], wikiFolderPath); -} -/** - * Add remote without credential - * @param {string} wikiFolderPath - * @param {string} githubRepoUrl - * @param {{ login: string, email: string, accessToken: string }} userInfo - */ -export async function credentialOff(wikiFolderPath: string): Promise { - const githubRepoUrl = await getRemoteUrl(wikiFolderPath); - const gitUrlWithOutCredential = getGitUrlWithOutCredential(githubRepoUrl); - await GitProcess.exec(['remote', 'set-url', 'origin', gitUrlWithOutCredential], wikiFolderPath); -} diff --git a/src/services/git/index.ts b/src/services/git/index.ts index 4289d026..28d6ba0e 100644 --- a/src/services/git/index.ts +++ b/src/services/git/index.ts @@ -1,17 +1,28 @@ import { ipcMain } from 'electron'; import { injectable, inject } from 'inversify'; -import { truncate, debounce } from 'lodash'; -import { GitProcess } from 'dugite'; -import isDev from 'electron-is-dev'; +import { debounce } from 'lodash'; +import { + AssumeSyncError, + CantSyncGitNotInitializedError, + CantSyncInSpecialGitStateAutoFixFailed, + clone, + commitAndSync, + getModifiedFileList, + getRemoteUrl, + GitPullPushError, + GitStep, + ILoggerContext, + initGit, + ModifiedFileList, + SyncParameterMissingError, + SyncScriptIsInDeadLoopError, +} from 'git-sync-js'; -import * as gitSync from './sync'; -import * as github from './github'; import serviceIdentifier from '@services/serviceIdentifier'; import type { IViewService } from '@services/view/interface'; import type { IPreferenceService } from '@services/preferences/interface'; import { logger } from '@services/libs/log'; import i18n from '@services/libs/i18n'; -import { getModifiedFileList, ModifiedFileList, getRemoteUrl } from './inspect'; import { IGitService, IGitUserInfos } from './interface'; import { defaultGitInfo } from './defaultGitInfo'; import { WikiChannel } from '@/constants/channels'; @@ -30,7 +41,7 @@ export class Git implements IGitService { }); } - public debounceCommitAndSync: (wikiFolderPath: string, githubRepoUrl: string, userInfo: IGitUserInfos) => Promise | undefined; + public debounceCommitAndSync: (wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos) => Promise | undefined; public async getWorkspacesRemote(wikiFolderPath: string): Promise { return await getRemoteUrl(wikiFolderPath); @@ -42,7 +53,7 @@ export class Git implements IGitService { /** * - * @param {string} githubRepoName similar to "linonetwo/wiki", string after "https://github.com/" + * @param {string} githubRepoName similar to "linonetwo/wiki", string after "https://com/" */ public async updateGitInfoTiddler(githubRepoName: string): Promise { const browserView = await this.viewService.getActiveBrowserView(); @@ -64,208 +75,178 @@ export class Git implements IGitService { logger.error('no browserView in updateGitInfoTiddler'); } - public async initWikiGit( - wikiFolderPath: string, - isMainWiki: boolean, - isSyncedWiki?: boolean, - githubRepoUrl?: string, - userInfo?: IGitUserInfos, - ): Promise { - const logProgress = (message: string): unknown => logger.notice(message, { handler: 'createWikiProgress', function: 'initWikiGit' }); - const logInfo = (message: string): unknown => logger.info(message, { function: 'initWikiGit' }); + private translateMessage(message: string): string { + switch (message) { + case GitStep.StartGitInitialization: { + return i18n.t('Log.StartGitInitialization'); + } + case GitStep.GitRepositoryConfigurationFinished: { + return i18n.t('Log.GitRepositoryConfigurationFinished'); + } + case GitStep.StartConfiguringGithubRemoteRepository: { + return i18n.t('Log.StartConfiguringGithubRemoteRepository'); + } + case GitStep.StartBackupToGitRemote: { + return i18n.t('Log.StartBackupToGithubRemote'); + } + case GitStep.PrepareCloneOnlineWiki: { + return i18n.t('Log.PrepareCloneOnlineWiki'); + } + case GitStep.PrepareSync: { + return i18n.t('Log.PrepareSync'); + } + case GitStep.HaveThingsToCommit: { + return i18n.t('Log.HaveThingsToCommit'); + } + case GitStep.AddingFiles: { + return i18n.t('Log.AddingFiles'); + } + case GitStep.AddComplete: { + return i18n.t('Log.AddComplete'); + } + case GitStep.CommitComplete: { + return i18n.t('Log.CommitComplete'); + } + case GitStep.PreparingUserInfo: { + return i18n.t('Log.PreparingUserInfo'); + } + case GitStep.FetchingData: { + return i18n.t('Log.FetchingData'); + } + case GitStep.NoNeedToSync: { + return i18n.t('Log.NoNeedToSync'); + } + case GitStep.LocalAheadStartUpload: { + return i18n.t('Log.LocalAheadStartUpload'); + } + case GitStep.CheckingLocalSyncState: { + return i18n.t('Log.CheckingLocalSyncState'); + } + case GitStep.CheckingLocalGitRepoSanity: { + return i18n.t('Log.CheckingLocalGitRepoSanity'); + } + case GitStep.LocalStateBehindSync: { + return i18n.t('Log.LocalStateBehindSync'); + } + case GitStep.LocalStateDivergeRebase: { + return i18n.t('Log.LocalStateDivergeRebase'); + } + case GitStep.RebaseResultChecking: { + return i18n.t('Log.CheckingRebaseStatus'); + } + case GitStep.RebaseConflictNeedsResolve: { + return i18n.t('Log.RebaseConflictNeedsResolve'); + } + case GitStep.RebaseSucceed: { + return i18n.t('Log.RebaseSucceed'); + } + case GitStep.GitPushFailed: { + return i18n.t('Log.GitPushFailed'); + } + case GitStep.GitMergeFailed: { + return i18n.t('Log.GitMergeFailed'); + } + case GitStep.SyncFailedAlgorithmWrong: { + return i18n.t('Log.SyncFailedSystemError'); + } + case GitStep.PerformLastCheckBeforeSynchronizationFinish: { + return i18n.t('Log.PerformLastCheckBeforeSynchronizationFinish'); + } + case GitStep.SynchronizationFinish: { + return i18n.t('Log.SynchronizationFinish'); + } + case GitStep.StartFetchingFromGithubRemote: { + return i18n.t('Log.StartFetchingFromGithubRemote'); + } + case GitStep.CantSyncInSpecialGitStateAutoFixSucceed: { + return i18n.t('Log.CantSyncInSpecialGitStateAutoFixSucceed'); + } + default: { + return message; + } + } + } - logProgress(i18n.t('Log.StartGitInitialization')); - const { gitUserName, email } = userInfo ?? defaultGitInfo; - await GitProcess.exec(['init'], wikiFolderPath); - await gitSync.commitFiles(wikiFolderPath, gitUserName, email); + private translateErrorMessage(error: Error): void { + logger.error(error?.message ?? error); + if (error instanceof AssumeSyncError) { + error.message = i18n.t('Log.SynchronizationFailed'); + } else if (error instanceof SyncParameterMissingError) { + error.message = i18n.t('Log.GitTokenMissing') + error.parameterName; + } else if (error instanceof GitPullPushError) { + error.message = i18n.t('Log.SyncFailedSystemError'); + } else if (error instanceof CantSyncGitNotInitializedError) { + error.message = i18n.t('Log.CantSyncGitNotInitialized'); + } else if (error instanceof SyncScriptIsInDeadLoopError) { + error.message = i18n.t('Log.CantSynchronizeAndSyncScriptIsInDeadLoop'); + } else if (error instanceof CantSyncInSpecialGitStateAutoFixFailed) { + error.message = i18n.t('Log.CantSyncInSpecialGitStateAutoFixFailed'); + } + logger.error('↑Translated→: ' + error?.message ?? error); + throw error; + } - // if we are config local wiki, we are done here - if (isSyncedWiki !== true) { - logProgress(i18n.t('Log.GitRepositoryConfigurationFinished')); - return; - } - // start config synced wiki - if (userInfo?.accessToken === undefined) { - throw new Error(i18n.t('Log.GitTokenMissing') + 'accessToken'); - } - if (githubRepoUrl === undefined) { - throw new Error(i18n.t('Log.GitTokenMissing') + 'githubRepoUrl'); - } - logInfo( - `Using gitUrl ${githubRepoUrl ?? 'githubRepoUrl unset'} with gitUserName ${gitUserName} and accessToken ${truncate(userInfo?.accessToken, { - length: 24, - })}`, - ); - logProgress(i18n.t('Log.StartConfiguringGithubRemoteRepository')); - await github.credentialOn(wikiFolderPath, githubRepoUrl, gitUserName, userInfo.accessToken); - logProgress(i18n.t('Log.StartBackupToGithubRemote')); - const defaultBranchName = await gitSync.getDefaultBranchName(wikiFolderPath); - const { stderr: pushStdError, exitCode: pushExitCode } = await GitProcess.exec( - ['push', 'origin', `${defaultBranchName}:${defaultBranchName}`], - wikiFolderPath, - ); - await github.credentialOff(wikiFolderPath); - if (isMainWiki && pushExitCode !== 0) { - logInfo(pushStdError); - const CONFIG_FAILED_MESSAGE = i18n.t('Log.GitRepositoryConfigurateFailed'); - logProgress(CONFIG_FAILED_MESSAGE); - throw new Error(CONFIG_FAILED_MESSAGE); - } else { - logProgress(i18n.t('Log.GitRepositoryConfigurationFinished')); + public async initWikiGit(wikiFolderPath: string, isMainWiki: boolean, isSyncedWiki?: boolean, remoteUrl?: string, userInfo?: IGitUserInfos): Promise { + try { + await initGit({ + dir: wikiFolderPath, + remoteUrl, + syncImmediately: isSyncedWiki, + userInfo: { ...defaultGitInfo, ...userInfo }, + logger: { + log: (message: string, context: ILoggerContext): unknown => logger.info(message, { callerFunction: 'initWikiGit', ...context }), + warn: (message: string, context: ILoggerContext): unknown => logger.warn(message, { callerFunction: 'initWikiGit', ...context }), + info: (message: GitStep, context: ILoggerContext): void => { + logger.notice(this.translateMessage(message), { handler: WikiChannel.syncProgress, callerFunction: 'initWikiGit', ...context }); + }, + }, + }); + } catch (error) { + this.translateErrorMessage(error); } } /** * * @param {string} wikiFolderPath - * @param {string} githubRepoUrl + * @param {string} remoteUrl * @param {{ login: string, email: string, accessToken: string }} userInfo */ - public async commitAndSync(wikiFolderPath: string, githubRepoUrl: string, userInfo: IGitUserInfos): Promise { - /** functions to send data to main thread */ - const logProgress = (message: string): unknown => - logger.notice(message, { handler: 'wikiSyncProgress', function: 'commitAndSync', wikiFolderPath, githubRepoUrl }); - const logInfo = (message: string): unknown => logger.info(message, { function: 'commitAndSync', wikiFolderPath, githubRepoUrl }); - - if (this.disableSyncOnDevelopment && isDev) { - return; + public async commitAndSync(wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos): Promise { + try { + await commitAndSync({ + dir: wikiFolderPath, + remoteUrl, + userInfo: { ...defaultGitInfo, ...userInfo }, + logger: { + log: (message: string, context: ILoggerContext): unknown => logger.info(message, { callerFunction: 'commitAndSync', ...context }), + warn: (message: string, context: ILoggerContext): unknown => logger.warn(message, { callerFunction: 'commitAndSync', ...context }), + info: (message: GitStep, context: ILoggerContext): void => { + logger.notice(this.translateMessage(message), { handler: WikiChannel.syncProgress, callerFunction: 'commitAndSync', ...context }); + }, + }, + }); + } catch (error) { + this.translateErrorMessage(error); } - const { gitUserName, email, accessToken } = userInfo; - if (accessToken === '' || accessToken === undefined) { - throw new Error(i18n.t('Log.GitTokenMissing')); - } - const commitMessage = 'Wiki updated with TiddlyGit-Desktop'; - const defaultBranchName = await gitSync.getDefaultBranchName(wikiFolderPath); - const branchMapping = `${defaultBranchName}:${defaultBranchName}`; - // update git info tiddler for plugins to use, for example, linonetwo/github-external-image - let wikiRepoName = new URL(githubRepoUrl).pathname; - if (wikiRepoName.startsWith('/')) { - wikiRepoName = wikiRepoName.replace('/', ''); - } - if (wikiRepoName.length > 0) { - await this.updateGitInfoTiddler(wikiRepoName); - } - // preflight check - const repoStartingState = await gitSync.getGitRepositoryState(wikiFolderPath, logInfo, logProgress); - if (repoStartingState.length > 0 || repoStartingState === '|DIRTY') { - const SYNC_MESSAGE = i18n.t('Log.PrepareSync'); - logProgress(SYNC_MESSAGE); - logInfo(`${SYNC_MESSAGE} ${wikiFolderPath} , ${gitUserName} <${email}>`); - } else if (repoStartingState === 'NOGIT') { - const CANT_SYNC_MESSAGE = i18n.t('Log.CantSyncGitNotInitialized'); - logProgress(CANT_SYNC_MESSAGE); - throw new Error(CANT_SYNC_MESSAGE); - } else { - // we may be in middle of a rebase, try fix that - await gitSync.continueRebase(wikiFolderPath, gitUserName, email, logInfo, logProgress); - } - if (await gitSync.haveLocalChanges(wikiFolderPath)) { - const SYNC_MESSAGE = i18n.t('Log.HaveThingsToCommit'); - logProgress(SYNC_MESSAGE); - logInfo(`${SYNC_MESSAGE} ${commitMessage}`); - const { exitCode: commitExitCode, stderr: commitStdError } = await gitSync.commitFiles(wikiFolderPath, gitUserName, email, commitMessage); - if (commitExitCode !== 0) { - logInfo('commit failed'); - logInfo(commitStdError); - } - logProgress(i18n.t('Log.CommitComplete')); - } - logProgress(i18n.t('Log.PreparingUserInfo')); - await github.credentialOn(wikiFolderPath, githubRepoUrl, gitUserName, accessToken); - logProgress(i18n.t('Log.FetchingData')); - await GitProcess.exec(['fetch', 'origin', defaultBranchName], wikiFolderPath); - // - switch (await gitSync.getSyncState(wikiFolderPath, logInfo)) { - case 'noUpstream': { - logProgress(i18n.t('Log.CantSyncGitNotInitialized')); - await github.credentialOff(wikiFolderPath); - return; - } - case 'equal': { - logProgress(i18n.t('Log.NoNeedToSync')); - await github.credentialOff(wikiFolderPath); - return; - } - case 'ahead': { - logProgress(i18n.t('Log.LocalAheadStartUpload')); - const { exitCode, stderr } = await GitProcess.exec(['push', 'origin', branchMapping], wikiFolderPath); - if (exitCode === 0) { - break; - } - logProgress(i18n.t('Log.GitPushFailed')); - logInfo(`exitCode: ${exitCode}, stderr of git push:`); - logInfo(stderr); - break; - } - case 'behind': { - logProgress(i18n.t('Log.LocalStateBehindSync')); - const { exitCode, stderr } = await GitProcess.exec(['merge', '--ff', '--ff-only', `origin/${defaultBranchName}`], wikiFolderPath); - if (exitCode === 0) { - break; - } - logProgress(i18n.t('Log.GitMergeFailed')); - logInfo(`exitCode: ${exitCode}, stderr of git merge:`); - logInfo(stderr); - break; - } - case 'diverged': { - logProgress(i18n.t('Log.LocalStateDivergeRebase')); - const { exitCode } = await GitProcess.exec(['rebase', `origin/${defaultBranchName}`], wikiFolderPath); - if ( - exitCode === 0 && - (await gitSync.getGitRepositoryState(wikiFolderPath, logInfo, logProgress)).length === 0 && - (await gitSync.getSyncState(wikiFolderPath, logInfo)) === 'ahead' - ) { - logProgress(i18n.t('Log.RebaseSucceed')); - } else { - await gitSync.continueRebase(wikiFolderPath, gitUserName, email, logInfo, logProgress); - logProgress(i18n.t('Log.RebaseConflictNeedsResolve')); - } - await GitProcess.exec(['push', 'origin', branchMapping], wikiFolderPath); - break; - } - default: { - logProgress(i18n.t('Log.SyncFailedSystemError')); - } - } - await github.credentialOff(wikiFolderPath); - logProgress(i18n.t('Log.PerformLastCheckBeforeSynchronizationFinish')); - await gitSync.assumeSync(wikiFolderPath, logInfo, logProgress); - logProgress(i18n.t('Log.SynchronizationFinish')); } - public async clone(githubRepoUrl: string, repoFolderPath: string, userInfo: IGitUserInfos): Promise { - const logProgress = (message: string): unknown => logger.notice(message, { handler: 'createWikiProgress', function: 'clone' }); - const logInfo = (message: string): unknown => logger.info(message, { function: 'clone' }); - logProgress(i18n.t('Log.PrepareCloneOnlineWiki')); - logProgress(i18n.t('Log.StartGitInitialization')); - const { gitUserName, accessToken } = userInfo; - if (accessToken === '' || accessToken === undefined) { - throw new Error(i18n.t('Log.GitTokenMissing')); - } - logInfo( - i18n.t('Log.UsingUrlAnduserName', { - githubRepoUrl, - gitUserName, - accessToken: truncate(accessToken, { - length: 24, - }), - }), - ); - await GitProcess.exec(['init'], repoFolderPath); - logProgress(i18n.t('Log.StartConfiguringGithubRemoteRepository')); - await github.credentialOn(repoFolderPath, githubRepoUrl, gitUserName, accessToken); - logProgress(i18n.t('Log.StartFetchingFromGithubRemote')); - const defaultBranchName = await gitSync.getDefaultBranchName(repoFolderPath); - const { stderr, exitCode } = await GitProcess.exec(['pull', 'origin', `${defaultBranchName}:${defaultBranchName}`], repoFolderPath); - await github.credentialOff(repoFolderPath); - if (exitCode !== 0) { - logInfo(stderr); - const CONFIG_FAILED_MESSAGE = i18n.t('Log.GitRepositoryConfigurateFailed'); - logProgress(CONFIG_FAILED_MESSAGE); - throw new Error(CONFIG_FAILED_MESSAGE); - } else { - logProgress(i18n.t('Log.GitRepositoryConfigurationFinished')); + public async clone(remoteUrl: string, repoFolderPath: string, userInfo: IGitUserInfos): Promise { + try { + await clone({ + dir: repoFolderPath, + remoteUrl, + userInfo: { ...defaultGitInfo, ...userInfo }, + logger: { + log: (message: string, context: ILoggerContext): unknown => logger.info(message, { callerFunction: 'clone', ...context }), + warn: (message: string, context: ILoggerContext): unknown => logger.warn(message, { callerFunction: 'clone', ...context }), + info: (message: GitStep, context: ILoggerContext): void => { + logger.notice(this.translateMessage(message), { handler: WikiChannel.syncProgress, callerFunction: 'clone', ...context }); + }, + }, + }); + } catch (error) { + this.translateErrorMessage(error); } } } diff --git a/src/services/git/inspect.ts b/src/services/git/inspect.ts deleted file mode 100644 index 999fdaf7..00000000 --- a/src/services/git/inspect.ts +++ /dev/null @@ -1,38 +0,0 @@ -import path from 'path'; -import { compact } from 'lodash'; -import { GitProcess } from 'dugite'; - -export interface ModifiedFileList { - type: string; - fileRelativePath: string; - filePath: string; -} -/** - * Get modified files and modify type in a folder - * @param {string} wikiFolderPath location to scan git modify state - */ -export async function getModifiedFileList(wikiFolderPath: string): Promise { - const { stdout } = await GitProcess.exec(['status', '--porcelain'], wikiFolderPath); - const stdoutLines = stdout.split('\n'); - return compact(compact(stdoutLines).map((line) => /^\s?(\?\?|[ACMR]|[ACMR][DM])\s?(\S+)$/.exec(line))).map(([_, type, fileRelativePath]) => ({ - type, - fileRelativePath, - filePath: path.join(wikiFolderPath, fileRelativePath), - })); -} - -/** - * Inspect git's remote url from folder's .git config - * @param wikiFolderPath git folder to inspect - * @returns remote url - */ -export async function getRemoteUrl(wikiFolderPath: string): Promise { - const { stdout: remoteStdout } = await GitProcess.exec(['remote'], wikiFolderPath); - const remotes = compact(remoteStdout.split('\n')); - const githubRemote = remotes.find((remote) => remote === 'origin') ?? remotes[0] ?? ''; - if (githubRemote.length > 0) { - const { stdout: remoteUrlStdout } = await GitProcess.exec(['remote', 'get-url', githubRemote], wikiFolderPath); - return remoteUrlStdout.replace('.git', ''); - } - return ''; -} diff --git a/src/services/git/interface.ts b/src/services/git/interface.ts index 11435008..6750a2fc 100644 --- a/src/services/git/interface.ts +++ b/src/services/git/interface.ts @@ -1,6 +1,6 @@ import { ProxyPropertyType } from '@/helpers/electron-ipc-proxy/common'; import { GitChannel } from '@/constants/channels'; -import { ModifiedFileList } from './inspect'; +import { ModifiedFileList } from 'git-sync-js'; export interface IGitUserInfos extends IGitUserInfosWithoutToken { /** Github Login: token */ @@ -22,18 +22,18 @@ export interface IGitService { /** * Call commitAndSync every period of time. This cannot be used as promise, as said in https://github.com/lodash/lodash/issues/4700 */ - debounceCommitAndSync: (wikiFolderPath: string, githubRepoUrl: string, userInfo: IGitUserInfos) => Promise | undefined; + debounceCommitAndSync: (wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos) => Promise | undefined; updateGitInfoTiddler(githubRepoName: string): Promise; getModifiedFileList(wikiFolderPath: string): Promise; /** * Run git init in a folder, prepare remote origin if isSyncedWiki */ - initWikiGit(wikiFolderPath: string, isMainWiki: boolean, isSyncedWiki: true, githubRepoUrl: string, userInfo: IGitUserInfos): Promise; + initWikiGit(wikiFolderPath: string, isMainWiki: boolean, isSyncedWiki: true, remoteUrl: string, userInfo: IGitUserInfos): Promise; initWikiGit(wikiFolderPath: string, isMainWiki: boolean, isSyncedWiki?: false): Promise; - commitAndSync(wikiFolderPath: string, githubRepoUrl: string, userInfo: IGitUserInfos): Promise; + commitAndSync(wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos): Promise; /** Inspect git's remote url from folder's .git config */ getWorkspacesRemote(wikiFolderPath: string): Promise; - clone(githubRepoUrl: string, repoFolderPath: string, userInfo: IGitUserInfos): Promise; + clone(remoteUrl: string, repoFolderPath: string, userInfo: IGitUserInfos): Promise; } export const GitServiceIPCDescriptor = { channel: GitChannel.name, diff --git a/src/services/git/sync.ts b/src/services/git/sync.ts deleted file mode 100644 index cad075cf..00000000 --- a/src/services/git/sync.ts +++ /dev/null @@ -1,199 +0,0 @@ -/* eslint-disable unicorn/consistent-function-scoping */ -/* eslint-disable no-await-in-loop */ -import fs from 'fs-extra'; -import path from 'path'; -import { compact } from 'lodash'; -import { GitProcess, IGitResult } from 'dugite'; - -import i18n from '@services/libs/i18n'; - -/** - * Get "master" or "main" from git repo - * @param wikiFolderPath - */ -export async function getDefaultBranchName(wikiFolderPath: string): Promise { - const { stdout } = await GitProcess.exec(['remote', 'show', 'origin'], wikiFolderPath); - const lines = stdout.split('\n'); - const lineWithHEAD = lines.find((line) => line.includes('HEAD branch: ')); - const branchName = lineWithHEAD?.replace('HEAD branch: ', '')?.replace(/\s/g, ''); - if (branchName === undefined || branchName.includes('(unknown)')) { - return 'master'; - } - return branchName; -} -/** - * Git add and commit all file - * @param wikiFolderPath - * @param username - * @param email - * @param message - */ -export async function commitFiles(wikiFolderPath: string, username: string, email: string, message = 'Initialize with TiddlyGit-Desktop'): Promise { - await GitProcess.exec(['add', '.'], wikiFolderPath); - return await GitProcess.exec(['commit', '-m', message, `--author="${username} <${email}>"`], wikiFolderPath); -} - -/** - * See if there is any file not being committed - * @param {string} wikiFolderPath repo path to test - */ -export async function haveLocalChanges(wikiFolderPath: string): Promise { - const { stdout } = await GitProcess.exec(['status', '--porcelain'], wikiFolderPath); - const matchResult = stdout.match(/^(\?\?|[ACMR] |[ ACMR][DM])*/gm); - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - return !!matchResult?.some((match) => Boolean(match)); -} - -export type SyncState = 'noUpstream' | 'equal' | 'ahead' | 'behind' | 'diverged'; -/** - * determine sync state of repository, i.e. how the remote relates to our HEAD - * 'ahead' means our local state is ahead of remote, 'behind' means local state is behind of the remote - * @param wikiFolderPath repo path to test - */ -export async function getSyncState(wikiFolderPath: string, logInfo: (message: string) => unknown): Promise { - const defaultBranchName = await getDefaultBranchName(wikiFolderPath); - const { stdout } = await GitProcess.exec(['rev-list', '--count', '--left-right', `origin/${defaultBranchName}...HEAD`], wikiFolderPath); - logInfo('Checking sync state with upstream'); - logInfo(`stdout:\n${stdout}\n(stdout end)`); - if (stdout === '') { - return 'noUpstream'; - } - if (/0\t0/.exec(stdout) !== null) { - return 'equal'; - } - if (/0\t\d+/.exec(stdout) !== null) { - return 'ahead'; - } - if (/\d+\t0/.exec(stdout) !== null) { - return 'behind'; - } - return 'diverged'; -} - -export async function assumeSync(wikiFolderPath: string, logInfo: (message: string) => unknown, logProgress: (message: string) => unknown): Promise { - if ((await getSyncState(wikiFolderPath, logInfo)) === 'equal') { - return; - } - const SYNC_ERROR_MESSAGE = i18n.t('Log.SynchronizationFailed'); - logProgress(SYNC_ERROR_MESSAGE); - throw new Error(SYNC_ERROR_MESSAGE); -} - -/** - * echo the git dir - * @param wikiFolderPath repo path - */ -async function getGitDirectory(wikiFolderPath: string, logInfo: (message: string) => unknown, logProgress: (message: string) => unknown): Promise { - const { stdout, stderr } = await GitProcess.exec(['rev-parse', '--is-inside-work-tree', wikiFolderPath], wikiFolderPath); - if (typeof stderr === 'string' && stderr.length > 0) { - logInfo(stderr); - } - if (stdout.startsWith('true')) { - const { stdout: stdout2 } = await GitProcess.exec(['rev-parse', '--git-dir', wikiFolderPath], wikiFolderPath); - const [gitPath2, gitPath1] = compact(stdout2.split('\n')); - if (gitPath1.length > 0 && gitPath2.length > 0) { - return path.resolve(`${gitPath1}/${gitPath2}`); - } - } - const CONFIG_FAILED_MESSAGE = i18n.t('Log.NotAGitRepository'); - logProgress(CONFIG_FAILED_MESSAGE); - throw new Error(`${wikiFolderPath} ${CONFIG_FAILED_MESSAGE}`); -} - -/** - * get various repo state in string format - * @param wikiFolderPath repo path to check - * @returns gitState - * // TODO: use template literal type to get exact type of git state - */ -export async function getGitRepositoryState( - wikiFolderPath: string, - logInfo: (message: string) => unknown, - logProgress: (message: string) => unknown, -): Promise { - const gitDirectory = await getGitDirectory(wikiFolderPath, logInfo, logProgress); - if (typeof gitDirectory !== 'string' || gitDirectory.length === 0) { - return 'NOGIT'; - } - let result = ''; - if (((await fs.lstat(path.join(gitDirectory, 'rebase-merge', 'interactive')).catch(() => ({}))) as fs.Stats)?.isFile()) { - result += 'REBASE-i'; - } else if (((await fs.lstat(path.join(gitDirectory, 'rebase-merge')).catch(() => ({}))) as fs.Stats)?.isDirectory()) { - result += 'REBASE-m'; - } else { - if (((await fs.lstat(path.join(gitDirectory, 'rebase-apply')).catch(() => ({}))) as fs.Stats)?.isDirectory()) { - result += 'AM/REBASE'; - } - if (((await fs.lstat(path.join(gitDirectory, 'MERGE_HEAD')).catch(() => ({}))) as fs.Stats)?.isFile()) { - result += 'MERGING'; - } - if (((await fs.lstat(path.join(gitDirectory, 'CHERRY_PICK_HEAD')).catch(() => ({}))) as fs.Stats)?.isFile()) { - result += 'CHERRY-PICKING'; - } - if (((await fs.lstat(path.join(gitDirectory, 'BISECT_LOG')).catch(() => ({}))) as fs.Stats)?.isFile()) { - result += 'BISECTING'; - } - } - if ((await GitProcess.exec(['rev-parse', '--is-inside-git-dir', wikiFolderPath], wikiFolderPath)).stdout.startsWith('true')) { - result += (await GitProcess.exec(['rev-parse', '--is-bare-repository', wikiFolderPath], wikiFolderPath)).stdout.startsWith('true') ? '|BARE' : '|GIT_DIR'; - } else if ((await GitProcess.exec(['rev-parse', '--is-inside-work-tree', wikiFolderPath], wikiFolderPath)).stdout.startsWith('true')) { - const { exitCode } = await GitProcess.exec(['diff', '--no-ext-diff', '--quiet', '--exit-code'], wikiFolderPath); - // 1 if there were differences and 0 means no differences. - if (exitCode !== 0) { - result += '|DIRTY'; - } - } - return result; -} -/** - * try to continue rebase, simply adding and committing all things, leave them to user to resolve in the TiddlyWiki later. - * @param wikiFolderPath - * @param username - * @param email - */ -export async function continueRebase( - wikiFolderPath: string, - username: string, - email: string, - logInfo: (message: string) => unknown, - logProgress: (message: string) => unknown, -): Promise { - let hasNotCommittedConflict = true; - let rebaseContinueExitCode = 0; - let rebaseContinueStdError = ''; - let repositoryState = await getGitRepositoryState(wikiFolderPath, logInfo, logProgress); - // prevent infin loop, if there is some bug that I miss - let loopCount = 0; - while (hasNotCommittedConflict) { - loopCount += 1; - if (loopCount > 1000) { - const CANT_SYNC_MESSAGE = i18n.t('Log.CantSynchronizeAndSyncScriptIsInDeadLoop'); - logProgress(CANT_SYNC_MESSAGE); - throw new Error(CANT_SYNC_MESSAGE); - } - const { exitCode: commitExitCode, stderr: commitStdError } = await commitFiles( - wikiFolderPath, - username, - email, - 'Conflict files committed with TiddlyGit-Desktop', - ); - const rebaseContinueResult = await GitProcess.exec(['rebase', '--continue'], wikiFolderPath); - // get info for logging - rebaseContinueExitCode = rebaseContinueResult.exitCode; - rebaseContinueStdError = rebaseContinueResult.stderr; - const rebaseContinueStdOut = rebaseContinueResult.stdout; - repositoryState = await getGitRepositoryState(wikiFolderPath, logInfo, logProgress); - // if git add . + git commit failed or git rebase --continue failed - if (commitExitCode !== 0 || rebaseContinueExitCode !== 0) { - logInfo(`rebaseContinueStdError when ${repositoryState}`); - logInfo(rebaseContinueStdError); - logInfo(`commitStdError when ${repositoryState}`); - logInfo(commitStdError); - const CANT_SYNC_MESSAGE = i18n.t('Log.CantSyncInSpecialGitStateAutoFixFailed'); - logProgress(CANT_SYNC_MESSAGE); - throw new Error(`${repositoryState} ${CANT_SYNC_MESSAGE}`); - } - hasNotCommittedConflict = rebaseContinueStdError.startsWith('CONFLICT') || rebaseContinueStdOut.startsWith('CONFLICT'); - } - logProgress(i18n.t('Log.CantSyncInSpecialGitStateAutoFixSucceed')); -}