mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
WIP
This commit is contained in:
parent
9745d18b64
commit
305c8f0804
3 changed files with 151 additions and 6 deletions
|
|
@ -3,8 +3,9 @@ import { GITHUB_OAUTH } from 'env'
|
|||
import { Base64 } from 'js-base64'
|
||||
import { $ } from 'utils/$'
|
||||
import { configRef } from 'utils/config/helper'
|
||||
import { resolveGitAttributes } from 'utils/gitAttributes'
|
||||
import { resolveGitModules } from 'utils/gitSubmodule'
|
||||
import { sortFoldersToFront } from 'utils/treeParser'
|
||||
import { findGitAttributes, findGitModules, sortFoldersToFront } from 'utils/treeParser'
|
||||
import * as API from './API'
|
||||
import * as DOMHelper from './DOMHelper'
|
||||
import { getCommitTreeData } from './getCommitTreeData'
|
||||
|
|
@ -270,9 +271,8 @@ async function getRepositoryTreeData(
|
|||
})),
|
||||
)
|
||||
|
||||
const gitModules = root.contents?.find(
|
||||
item => item.type === 'blob' && item.name === '.gitmodules',
|
||||
)
|
||||
// TODO: cache
|
||||
const gitModules = findGitModules(root)
|
||||
if (gitModules?.sha) {
|
||||
const blobData = await API.getBlobData(userName, repoName, gitModules.sha, accessToken)
|
||||
|
||||
|
|
@ -281,5 +281,14 @@ async function getRepositoryTreeData(
|
|||
}
|
||||
}
|
||||
|
||||
const gitAttributes = findGitAttributes(root)
|
||||
if (gitAttributes?.sha) {
|
||||
const blobData = await API.getBlobData(userName, repoName, gitAttributes.sha, accessToken)
|
||||
|
||||
if (blobData && blobData.encoding === 'base64' && blobData.content) {
|
||||
resolveGitAttributes(root, Base64.decode(blobData.content))
|
||||
}
|
||||
}
|
||||
|
||||
return { root, defer: treeData.truncated }
|
||||
}
|
||||
|
|
|
|||
124
src/utils/gitAttributes.ts
Normal file
124
src/utils/gitAttributes.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// ref for file syntax, it basically inherits from `.gitignore`: https://www.git-scm.com/docs/gitignore#_pattern_format
|
||||
// ref for `.gitattributes` file: https://www.git-scm.com/docs/gitattributes#_description
|
||||
// ref for `linguist-generated` attribute https://github.com/github/linguist
|
||||
|
||||
type X<Value, Inputs extends any[]> = Value | ((...inputs: Inputs) => Value)
|
||||
|
||||
function createMachine<Input, States extends string, State>(
|
||||
stateCreator: () => State,
|
||||
map: {
|
||||
[key in States]?: X<States | null, [input: Input, state: State]>
|
||||
},
|
||||
) {
|
||||
return (input: Input): State => {
|
||||
const state = stateCreator()
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
// This function does not support all attributes defined for `.gitattributes` file
|
||||
// It is only used to recognize `linguist-generated` and filter files
|
||||
export function resolveGitAttributes(root: TreeNode, content: string) {
|
||||
content
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.forEach(line => {
|
||||
type States = 'init' | 'comment' | 'negative' | 'pre-patterns' | 'pattern' | 'c-pattern' | 'error'
|
||||
|
||||
const machine = createMachine<
|
||||
string,
|
||||
States,
|
||||
{
|
||||
pattern: string | null
|
||||
error: unknown
|
||||
negative: boolean
|
||||
}
|
||||
>(
|
||||
() => ({
|
||||
pattern: null,
|
||||
negative: false,
|
||||
error: null
|
||||
}),
|
||||
{
|
||||
/**
|
||||
* stateName: [
|
||||
* patternSwitcher: X<string | null, [char: string, state: State]>,
|
||||
* ]
|
||||
*/
|
||||
init:
|
||||
c =>
|
||||
(({
|
||||
'#': 'comment',
|
||||
'!': 'negative',
|
||||
} satisfies Partial<{
|
||||
[key: string]: States
|
||||
}>)[c] || 'pre-patterns'),
|
||||
comment: () => null,
|
||||
negative:
|
||||
(c, state) => {
|
||||
state.negative = true
|
||||
return 'pattern'
|
||||
},
|
||||
"pre-patterns": c => c === '"' ? 'c-pattern' : 'pattern',
|
||||
'c-pattern': c => null,
|
||||
pattern: (c, state) => {
|
||||
// run sub-machine
|
||||
state.pattern = state.pattern || ''
|
||||
return 'pattern';
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* A blank line matches no files, so it can serve as a separator for readability.
|
||||
*/
|
||||
if (line === '') return
|
||||
|
||||
/**
|
||||
* A line starting with # serves as a comment. Put a backslash ("\") in front of the first hash for patterns that begin with a hash.
|
||||
*/
|
||||
if (line[0] === '#') return
|
||||
|
||||
/**
|
||||
* Trailing spaces are ignored unless they are quoted with backslash ("\").
|
||||
*/
|
||||
|
||||
/**
|
||||
* An optional prefix "!" which negates the pattern;
|
||||
* any matching file excluded by a previous pattern will become included again.
|
||||
* It is not possible to re-include a file if a parent directory of that file is excluded.
|
||||
* Git doesn’t list excluded directories for performance reasons,
|
||||
* so any patterns on contained files have no effect, no matter where they are defined.
|
||||
* Put a backslash ("\") in front of the first "!" for patterns that begin with a literal "!", for example, "\!important!.txt".
|
||||
*/
|
||||
const negative = line[0] === '!'
|
||||
|
||||
/**
|
||||
* The slash / is used as the directory separator. Separators may occur at the beginning, middle or end of the `.gitignore` search pattern.
|
||||
*
|
||||
* If there is a separator at the beginning or middle (or both) of the pattern,
|
||||
* then the pattern is relative to the directory level of the particular `.gitignore` file itself.
|
||||
* Otherwise the pattern may also match at any level below the `.gitignore` level.
|
||||
* If there is a separator at the end of the pattern then the pattern will only match directories,
|
||||
* otherwise the pattern can match both files and directories.
|
||||
* For example, a pattern `doc/frotz/` matches `doc/frotz` directory, but not `a/doc/frotz` directory;
|
||||
* however `frotz/` matches `frotz` and `a/frotz` that is a directory (all paths are relative from the `.gitignore` file).
|
||||
*/
|
||||
|
||||
/**
|
||||
* An asterisk "*" matches anything except a slash.
|
||||
* The character "?" matches any one character except "/".
|
||||
* The range notation, e.g. [a-zA-Z], can be used to match one of the characters in a range.
|
||||
* See fnmatch(3) and the FNM_PATHNAME flag for a more detailed description.
|
||||
*/
|
||||
|
||||
|
||||
// Two consecutive asterisks ("**") in patterns matched against full pathname may have special meaning:
|
||||
// A leading "**" followed by a slash means match in all directories. For example, "**/foo" matches file or directory "foo" anywhere, the same as pattern "foo". "**/foo/bar" matches file or directory "bar" anywhere that is directly under directory "foo".
|
||||
// A trailing "/**" matches everything inside. For example, "abc/**" matches all files inside directory "abc", relative to the location of the .gitignore file, with infinite depth.
|
||||
// A slash followed by two consecutive asterisks then a slash matches zero or more directories. For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on.
|
||||
// Other consecutive asterisks are considered regular asterisks and will match according to the previous rules.
|
||||
|
||||
// find `linguist-generated=true` and guarantee no `linguist-generated=false` after it
|
||||
})
|
||||
}
|
||||
|
|
@ -11,10 +11,22 @@ export function sortFoldersToFront(root: TreeNode) {
|
|||
|
||||
export function findGitModules(root: TreeNode) {
|
||||
if (root.contents) {
|
||||
const modulesFile = root.contents.find(content => content.name === '.gitmodules')
|
||||
const modulesFile = root.contents.find(
|
||||
content => content.type === 'blob' && content.name === '.gitmodules',
|
||||
)
|
||||
if (modulesFile) {
|
||||
return modulesFile
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function findGitAttributes(root: TreeNode) {
|
||||
if (root.contents) {
|
||||
const attributesFile = root.contents.find(
|
||||
content => content.type === 'blob' && content.name === '.gitattributes',
|
||||
)
|
||||
if (attributesFile) {
|
||||
return attributesFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue