Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 47 additions & 126 deletions assets/scripts/GitAgent.js → assets/scripts/GitAgent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//@ts-check

/**
* Ce fichier gère les interactions avec git (contenu, commits, branches, remotes, pull/pull, etc.)
* et aussi le contenu sous-jacent et le filesystem
Expand All @@ -17,8 +15,8 @@ import http from 'isomorphic-git/http/web'

const DEFAULT_CORS_PROXY_URL = 'https://cors.isomorphic-git.org'

/** @typedef {import('isomorphic-git')} isomorphicGit */
/** @typedef {import('isomorphic-git').GitAuth} GitAuth */
import type { CommitObject, GitAuth } from 'isomorphic-git'
import type { ResolutionOption } from './store.ts'

export default class GitAgent {
#fs
Expand All @@ -33,20 +31,20 @@ export default class GitAgent {
#hostname
#repoDirectory

/**
* @param { object } _
* @param { string } _.repoId
* @param { string } _.remoteURL
* @param { string } [_.corsProxyURL]
* @param { GitAuth } _.auth
* @param {((resolutionOptions: import('./store.js').ResolutionOption[]) => void) | undefined } [_.onMergeConflict]
*/
constructor({
repoId,
remoteURL,
corsProxyURL = DEFAULT_CORS_PROXY_URL,
auth,
onMergeConflict,
}: {
repoId: string
remoteURL: string
corsProxyURL?: string
auth: GitAuth
onMergeConflict?:
| ((resolutionOptions: ResolutionOption[]) => void)
| undefined
}) {
this.#fs = new FS('scribouilli')

Expand All @@ -63,31 +61,18 @@ export default class GitAgent {
this.#repoDirectory = `/${this.#hostname}/${this.#repoId}`
}

/**
*
* @param {string} filename
* @returns {string}
*/
#path(filename) {
#path(filename: string): string {
return `${this.#repoDirectory}/${filename}`
}

/**
* @summary helper to create ref strings for remotes
*
* @param {string} remote
* @param {string} ref
* @returns {string}
*/
#createRemoteRef(remote, ref) {
#createRemoteRef(remote: string, ref: string): string {
return `remotes/${remote}/${ref}`
}

/**
*
* @returns {ReturnType<isomorphicGit["clone"]>}
*/
clone() {
clone(): ReturnType<typeof git.clone> {
console.info('clone', this.#remoteURL)
return git.clone({
fs: this.#fs,
Expand All @@ -101,25 +86,18 @@ export default class GitAgent {
})
}

/**
*
* @returns {ReturnType<isomorphicGit["currentBranch"]>}
*/
currentBranch() {
currentBranch(): ReturnType<typeof git.currentBranch> {
return git.currentBranch({
fs: this.#fs,
dir: this.#repoDirectory,
})
}

/**
*
* @param {string} branch
* @param {boolean} [force]
* @param {boolean} [checkout]
* @returns {ReturnType<isomorphicGit["branch"]>}
*/
branch(branch, force = false, checkout = true) {
branch(
branch: string,
force: boolean = false,
checkout: boolean = true,
): ReturnType<typeof git.branch> {
return git.branch({
fs: this.#fs,
dir: this.#repoDirectory,
Expand All @@ -129,23 +107,14 @@ export default class GitAgent {
})
}

/**
*
* @returns {ReturnType<isomorphicGit["listRemotes"]>}
*/
listRemotes() {
listRemotes(): ReturnType<typeof git.listRemotes> {
return git.listRemotes({
fs: this.#fs,
dir: this.#repoDirectory,
})
}

/**
*
* @param {string} [remote]
* @returns {ReturnType<isomorphicGit["listBranches"]>}
*/
listBranches(remote) {
listBranches(remote: string): ReturnType<typeof git.listBranches> {
return git.listBranches({
fs: this.#fs,
dir: this.#repoDirectory,
Expand All @@ -156,10 +125,8 @@ export default class GitAgent {
/**
* @summary This version of git push may fail if the remote repo
* has unmerged changes
*
* @returns {ReturnType<isomorphicGit["push"]>}
*/
falliblePush() {
falliblePush(): ReturnType<typeof git.push> {
console.info('falliblePush')
return git.push({
fs: this.#fs,
Expand All @@ -176,10 +143,8 @@ export default class GitAgent {
* @summary This version of git push tries to push
* then tries to pull if the push fails
* and tries again to push if the pull succeeded
*
* @returns {Promise<any>}
*/
safePush() {
safePush(): Promise<any> {
console.info('safePush')
return this.falliblePush()
.catch(err => {
Expand All @@ -203,10 +168,8 @@ export default class GitAgent {

/**
* @summary like git push --force
*
* @returns {ReturnType<isomorphicGit["push"]>}
*/
forcePush() {
forcePush(): ReturnType<typeof git.push> {
console.info('forcePush')
return git.push({
fs: this.#fs,
Expand All @@ -220,11 +183,7 @@ export default class GitAgent {
})
}

/**
*
* @returns {ReturnType<isomorphicGit["fetch"]>}
*/
async fetch() {
async fetch(): ReturnType<typeof git.fetch> {
return git.fetch({
fs: this.#fs,
http,
Expand All @@ -235,12 +194,9 @@ export default class GitAgent {
})
}

/**
*
* @param {string} [ref]
* @returns {Promise<import('isomorphic-git').CommitObject & { oid: string }>}
*/
currentCommit(ref = undefined) {
currentCommit(
ref: string | undefined = undefined,
): Promise<CommitObject & { oid: string }> {
return git
.log({
fs: this.#fs,
Expand All @@ -251,12 +207,9 @@ export default class GitAgent {
.then(commits => ({ oid: commits[0].oid, ...commits[0].commit }))
}

/**
*
* @param {string} [ref]
* @returns {ReturnType<isomorphicGit["checkout"]>}
*/
checkout(ref = undefined) {
checkout(
ref: string | undefined = undefined,
): ReturnType<typeof git.checkout> {
return git.checkout({
fs: this.#fs,
dir: this.#repoDirectory,
Expand All @@ -265,13 +218,10 @@ export default class GitAgent {
}

/**
*
* @summary This function tries to merge
* If it fails, it forwards the conflict to this.onMergeConflict with resolution propositions
*
* @returns {Promise<any>}
*/
async tryMerging() {
async tryMerging(): Promise<any> {
console.info('tryMerging')
const [currentBranch, remotes] = await Promise.all([
this.currentBranch(),
Expand Down Expand Up @@ -338,12 +288,8 @@ export default class GitAgent {

/**
* @summary Create a commit with the given message.
*
* @param {string} message
*
* @returns {ReturnType<isomorphicGit["commit"]>} sha of the commit
*/
commit(message) {
commit(message: string): ReturnType<typeof git.commit> {
return git.commit({
fs: this.#fs,
dir: this.#repoDirectory,
Expand All @@ -353,11 +299,8 @@ export default class GitAgent {

/**
* @summary Remove file from git tree and from the file system
*
* @param {string} fileName
* @returns {ReturnType<isomorphicGit["remove"]>}
*/
async removeFile(fileName) {
async removeFile(fileName: string): ReturnType<typeof git.remove> {
const path = this.#path(fileName)
await this.#fs.promises.unlink(path)
return await git.remove({
Expand All @@ -369,19 +312,13 @@ export default class GitAgent {

/**
* @summary like a git pull but the merge is better customized
*
* @returns {Promise<any>}
*/
async fetchAndTryMerging() {
async fetchAndTryMerging(): Promise<any> {
await this.fetch()
await this.tryMerging()
}

/**
*
* @return {Promise<any>}
*/
async pullOrCloneRepo() {
async pullOrCloneRepo(): Promise<any> {
let dirExists = true
try {
const stat = await this.#fs.promises.stat(this.#repoDirectory)
Expand All @@ -408,12 +345,11 @@ export default class GitAgent {
* https://isomorphic-git.org/docs/en/setConfig
*
* Alors, on doit passer le repoName
*
* @param {string} login
* @param {string} email
* @returns {Promise<ReturnType<isomorphicGit["setConfig"]>>}
*/
async setAuthor(login, email) {
async setAuthor(
login: string,
email: string,
): Promise<ReturnType<typeof git.setConfig>> {
if (!login || !email) {
return
}
Expand All @@ -434,11 +370,8 @@ export default class GitAgent {

/**
* @summary Get file informations
*
* @param {string} fileName
* @returns {Promise<string>}
*/
async getFile(fileName) {
async getFile(fileName: string): Promise<string> {
const content = await this.#fs.promises.readFile(this.#path(fileName), {
encoding: 'utf8',
})
Expand All @@ -450,13 +383,11 @@ export default class GitAgent {

/**
* @summary Create or update a file and add it to the git staging area
*
* @param {string | Uint8Array} content
* @param {string} fileName
*
* @returns {Promise<void>}
*/
async writeFile(fileName, content) {
async writeFile(
fileName: string,
content: string | Uint8Array,
): Promise<void> {
// This condition is here just in case, but it should not happen in practice
// Having an empty file name will not lead immediately to a crash but will result in
// some bugs later, see https://github.com/Scribouilli/scribouilli/issues/49#issuecomment-1648226372
Expand All @@ -472,21 +403,11 @@ export default class GitAgent {
})
}

/**
*
* @param {string} dir
* @returns
*/
listFiles(dir) {
listFiles(dir: string) {
return this.#fs.promises.readdir(this.#path(dir))
}

/**
*
* @param {string} filename
* @returns
*/
async checkFileExistence(filename) {
async checkFileExistence(filename: string) {
const stat = await this.#fs.promises.stat(this.#path(filename))
return stat.isFile()
}
Expand Down
Loading