mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2026-03-05 13:30:49 -08:00
refactor: things into git-sync-js
This commit is contained in:
parent
1f2008be4e
commit
c2d4fad4e1
9 changed files with 330 additions and 494 deletions
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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}}"
|
||||
},
|
||||
|
|
|
|||
141
package-lock.json
generated
141
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
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<void> {
|
||||
const githubRepoUrl = await getRemoteUrl(wikiFolderPath);
|
||||
const gitUrlWithOutCredential = getGitUrlWithOutCredential(githubRepoUrl);
|
||||
await GitProcess.exec(['remote', 'set-url', 'origin', gitUrlWithOutCredential], wikiFolderPath);
|
||||
}
|
||||
|
|
@ -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<void> | undefined;
|
||||
public debounceCommitAndSync: (wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos) => Promise<void> | undefined;
|
||||
|
||||
public async getWorkspacesRemote(wikiFolderPath: string): Promise<string> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
/** 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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ModifiedFileList[]> {
|
||||
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<string> {
|
||||
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 '';
|
||||
}
|
||||
|
|
@ -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<void> | undefined;
|
||||
debounceCommitAndSync: (wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos) => Promise<void> | undefined;
|
||||
updateGitInfoTiddler(githubRepoName: string): Promise<void>;
|
||||
getModifiedFileList(wikiFolderPath: string): Promise<ModifiedFileList[]>;
|
||||
/**
|
||||
* Run git init in a folder, prepare remote origin if isSyncedWiki
|
||||
*/
|
||||
initWikiGit(wikiFolderPath: string, isMainWiki: boolean, isSyncedWiki: true, githubRepoUrl: string, userInfo: IGitUserInfos): Promise<void>;
|
||||
initWikiGit(wikiFolderPath: string, isMainWiki: boolean, isSyncedWiki: true, remoteUrl: string, userInfo: IGitUserInfos): Promise<void>;
|
||||
initWikiGit(wikiFolderPath: string, isMainWiki: boolean, isSyncedWiki?: false): Promise<void>;
|
||||
commitAndSync(wikiFolderPath: string, githubRepoUrl: string, userInfo: IGitUserInfos): Promise<void>;
|
||||
commitAndSync(wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos): Promise<void>;
|
||||
/** Inspect git's remote url from folder's .git config */
|
||||
getWorkspacesRemote(wikiFolderPath: string): Promise<string>;
|
||||
clone(githubRepoUrl: string, repoFolderPath: string, userInfo: IGitUserInfos): Promise<void>;
|
||||
clone(remoteUrl: string, repoFolderPath: string, userInfo: IGitUserInfos): Promise<void>;
|
||||
}
|
||||
export const GitServiceIPCDescriptor = {
|
||||
channel: GitChannel.name,
|
||||
|
|
|
|||
|
|
@ -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<string> {
|
||||
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<IGitResult> {
|
||||
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<boolean> {
|
||||
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<SyncState> {
|
||||
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<void> {
|
||||
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<string> {
|
||||
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<string> {
|
||||
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<void> {
|
||||
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'));
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue