Tucker McKnight
Remove darcs functionality :( I'd like to come back to darcs later, when I've come up with a way of making some kind of plugin system for making this work with multiple VCSes. In the meantime, development will be faster if I just focus on git.
62 63 64
const isDarcs = event.target.dataset.vcs === "darcs"
const copiedPrefix = isDarcs ? `darcs pull ${jsVars.baseUrl} -h ` : ""
navigator.clipboard.writeText(`${copiedPrefix}${hash}`).then(() => {
62 63 64
navigator.clipboard.writeText(hash).then(() => {
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
if (repoConfig._type === "darcs") {
for (let branch in repoConfig.branches) {
const repoPath = eleventyConfig.dir.output + reposPath + "/" + eleventyConfig.getFilter("slugify")(repoName) + "/branches/" + branch
const originalLocation = cwd + "/" + repoConfig.branches[branch].location
// If it is there, do darcs pull
if (fsImport.existsSync(repoPath + "/_darcs")) {
await exec(`(cd ${repoPath} && darcs pull --no-interactive)`)
} else {
// If it is not there, do darcs clone
await exec(`(cd ${repoPath} && mkdir -p temp; darcs clone ${originalLocation} temp/${branch} --no-working-dir; mv temp/${branch}/_darcs .; rm -R temp)`)
}
}
} else if (repoConfig._type === "git") {
const repoPath = eleventyConfig.dir.output + reposPath + "/" + eleventyConfig.getFilter("slugify")(repoName)
const gitRepoName = eleventyConfig.getFilter("slugify")(repoName) + ".git"
// If it is there, do git pull
if (fsImport.existsSync(repoPath + ".git")) {
// git repos are just in the repos folder, not in their subdir
// create string of commands saying 'git fetch origin branch:branch' for each branch
const fetchCommands = repoConfig.branchesToPull.map(branch => `git fetch origin ${branch}:${branch}`).join('; ')
await exec(`(cd ${eleventyConfig.dir.output + reposPath + "/" + gitRepoName} && ${fetchCommands}; git update-server-info)`)
} else {
// If it is not there, do git clone
const originalLocation = cwd + "/" + repoConfig.location
await exec(`(cd ${eleventyConfig.dir.output + reposPath + "/"} && git clone ${originalLocation} ${gitRepoName} --bare)`)
await exec(`(cd ${eleventyConfig.dir.output + reposPath + "/" + gitRepoName} && git update-server-info)`)
}
if (typeof repoConfig.artifactSteps !== 'undefined') {
// make a temp directory for things to run in
const tempDirName = `temp_${Math.floor(Math.random() * 10000).toString()}`
const tempDir = `${directories.output}${reposPath}${tempDirName}`
const tempDirRepoPath = `${tempDir}/${eleventyConfig.getFilter("slugify")(repoName)}`
const mkdirresult = await exec(`mkdir ${tempDir}`)
await exec(`git clone -s ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}.git ${tempDirRepoPath}`)
for (let branch of repoConfig.branchesToPull) {
await exec(`(cd ${tempDirRepoPath} && git checkout ${branch})`)
for (let artifactStep of repoConfig.artifactSteps) {
// Run the command for each step in each branch
await exec(`(cd ${tempDirRepoPath} && ${artifactStep.command})`)
// Copy the specified folders from the "from" to the "to" dir
await exec(`cp -r --remove-destination ${tempDirRepoPath}/${artifactStep.copyFrom} ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}/branches/${eleventyConfig.getFilter("slugify")(branch)}/${artifactStep.copyTo}`)
}
// delete the temp dirs
await exec(`rm -r ${tempDir}`)
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
const repoPath = eleventyConfig.dir.output + reposPath + "/" + eleventyConfig.getFilter("slugify")(repoName)
const gitRepoName = eleventyConfig.getFilter("slugify")(repoName) + ".git"
// If it is there, do git pull
if (fsImport.existsSync(repoPath + ".git")) {
// git repos are just in the repos folder, not in their subdir
// create string of commands saying 'git fetch origin branch:branch' for each branch
const fetchCommands = repoConfig.branchesToPull.map(branch => `git fetch origin ${branch}:${branch}`).join('; ')
await exec(`(cd ${eleventyConfig.dir.output + reposPath + "/" + gitRepoName} && ${fetchCommands}; git update-server-info)`)
} else {
// If it is not there, do git clone
const originalLocation = cwd + "/" + repoConfig.location
await exec(`(cd ${eleventyConfig.dir.output + reposPath + "/"} && git clone ${originalLocation} ${gitRepoName} --bare)`)
await exec(`(cd ${eleventyConfig.dir.output + reposPath + "/" + gitRepoName} && git update-server-info)`)
}
if (typeof repoConfig.artifactSteps !== 'undefined') {
// make a temp directory for things to run in
const tempDirName = `temp_${Math.floor(Math.random() * 10000).toString()}`
const tempDir = `${directories.output}${reposPath}${tempDirName}`
const tempDirRepoPath = `${tempDir}/${eleventyConfig.getFilter("slugify")(repoName)}`
const mkdirresult = await exec(`mkdir ${tempDir}`)
await exec(`git clone -s ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}.git ${tempDirRepoPath}`)
for (let branch of repoConfig.branchesToPull) {
await exec(`(cd ${tempDirRepoPath} && git checkout ${branch})`)
for (let artifactStep of repoConfig.artifactSteps) {
// Run the command for each step in each branch
await exec(`(cd ${tempDirRepoPath} && ${artifactStep.command})`)
// Copy the specified folders from the "from" to the "to" dir
await exec(`cp -r --remove-destination ${tempDirRepoPath}/${artifactStep.copyFrom} ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}/branches/${eleventyConfig.getFilter("slugify")(branch)}/${artifactStep.copyTo}`)
// delete the temp dirs
await exec(`rm -r ${tempDir}`)
210 211 212 213 214 215 216
let command = ''
if (config._type === "git") {
command = `git show ${branch}:${filename}`
}
else if (config._type === "darcs") {
command = `darcs show contents ${filename}`
}
210 211 212 213 214 215 216
const command = `git show ${branch}:${filename}`
237 238 239 240 241 242 243
let command = ''
if (config._type === "git") {
command = `git show ${branchName}:README.md`
}
else if (config._type === "darcs") {
command = `darcs show contents README.md`
}
237 238 239 240 241 242 243
const command = `git show ${branchName}:README.md`
10 11 12 13 14 15 16 17 18 19
window.jsVars['cloneDiv'] = `{%- if reposConfig.repos[nav.repoName]._type == "darcs" -%}{% set url = repos[nav.repoName].cloneUrl + (nav.branchName | slugify) %}
<label class="form-label">HTTPS URL</label>
<div class="input-group d-flex flex-nowrap">
<span class="clone overflow-hidden input-group-text">
{{ url }}
</span>
<button data-clone-url="{{url}}" class="btn btn-primary" id="clone-button">Copy</button>
</div>{%- elif reposConfig.repos[nav.repoName]._type == "git" -%}<label class="form-label">HTTPS URL</label>
</div>
{%- endif -%}`;
10 11 12 13 14 15 16 17 18 19
window.jsVars['cloneDiv'] = `<label class="form-label">HTTPS URL</label>
</div>`;
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
"DarcsConfig": {
"additionalProperties": false,
"description": "A configuration object for a darcs repository.",
"properties": {
"_type": {
"const": "darcs",
"description": "Must be set to `\"darcs\"`.",
"type": "string"
},
"artifactSteps": {
"items": {
"additionalProperties": false,
"properties": {
"command": {
"type": "string"
},
"copyFrom": {
"type": "string"
},
"copyTo": {
"type": "string"
}
},
"required": [
"command",
"copyFrom",
"copyTo"
],
"type": "object"
},
"type": "array"
},
"branches": {
"additionalProperties": {
"additionalProperties": false,
"description": "Each key in this object will be the name that is used for that branch.",
"properties": {
"description": {
"description": "A description of this branch. You may want to clarify what this branch is used for here.",
"type": "string"
},
"location": {
"description": "The absolute path to the repository for this branch.",
"type": "string"
}
},
"required": [
"location"
],
"type": "object"
},
"description": "The branches of this repository to generate pages for. Since darcs doesn't have branches in the same way that other VCSs do, a \"branch\" in this case is an entirely separate repository. So, these \"branches\" are more like repos that are grouped under the same project name. That is why you need to specify a separate location for each branch.",
"type": "object"
},
"defaultBranch": {
"description": "The name of the default branch. Should match one of the keys in {@link DarcsConfig.branches } .",
"type": "string"
},
"languageExtensions": {
"additionalProperties": {
"type": "string"
},
"description": "If your repository has any uncommon file extensions that should be treated like a different type of file, list them here. If you include `{njk: \"html\"}` here, that will tell the syntax highlighter to highlight an `njk` file like an `html` file. The key is the file extension in your code, and the value is the file extension that the syntax highlighter will know about.",
"type": "object"
}
},
"required": [
"_type",
"defaultBranch",
"branches"
],
"type": "object"
},
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
137
"description": "The ReposConfiguration object contains information about your local repositories, like their name and location on your local filesystem. Add repositories to this configuration object to make a static site for them.\n\nThis static site generator works with both git and darcs repositories. Because of the differences between these two version control systems, the configuration for them looks a little different. Both types of configuration objects can be nested underneath the {@link ReposConfiguration.repos } key.\n\nYou will also need to set the {@link ReposConfiguration.baseUrl } to the URL of your live website.",
137
"description": "The ReposConfiguration object contains information about your local repositories, like their name and location on your local filesystem. Add repositories to this configuration object to make a static site for them.\n\nYou will also need to set the {@link ReposConfiguration.baseUrl } to the URL of your live website.",
149 150 151 152 153 154 155 156 157
"anyOf": [
{
"$ref": "#/definitions/GitConfig"
},
{
"$ref": "#/definitions/DarcsConfig"
}
],
"description": "An object containing the configuration for your repositories. Each key in this object is a repository name, and the value has several config options for that repository. The required config options describe the path to the repository and which branches should be pulled. See the specific definitions of {@link GitConfig } and {@link DarcsConfig } for more details about what goes in these configuration objects."
149 150 151 152 153 154 155 156 157
"$ref": "#/definitions/GitConfig",
"description": "An object containing the configuration for your repositories. Each key in this object is a repository name, and the value has several config options for that repository. The required config options describe the path to the repository and which branches should be pulled. See the specific definition of {@link GitConfig } for more details about what goes in these configuration objects."
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
* This static site generator works with both git and darcs repositories. Because of the
* differences between these two version control systems, the configuration for them
* looks a little different. Both types of configuration objects can be nested underneath
* the {@link ReposConfiguration.repos} key.
*
* "My Darcs Project": {
* _type: "darcs",
* branches: {
* 'main': {
* location: "/home/alice/projects/my_darcs_project",
* description: "Main branch of this project."
* },
* 'drafts': {
* location: "/home/alice/projects/my_darcs_project_drafts/",
* description: "Some things that are a work in progress",
* },
* },
* languageExtensions: {
* "njk": "html",
* },
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
* "My Git Project": {
* _type: "git",
* branches: ['main', 'develop']
40 41
* definitions of {@link GitConfig} and {@link DarcsConfig} for more details
[repoName: string]: GitConfig | DarcsConfig
40 41
* definition of {@link GitConfig} for more details
[repoName: string]: GitConfig
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
/**
* A configuration object for a darcs repository.
*
* @example
* "My Darcs Project": {
* _type: "darcs",
* baseUrl: "https://repos.tuckerm.us",
* defaultBranch: 'main',
* branches: {
* 'main': {
* location: "/home/alice/projects/my_darcs_project",
* description: "Main branch of this project."
* },
* 'drafts': {
* location: "/home/alice/projects/my_darcs_project_drafts/",
* description: "Some things that are a work in progress",
* },
* },
* languageExtensions: {
* "njk": "html",
* },
* }
*/
export type DarcsConfig = {
/** Must be set to `"darcs"`. */
_type: "darcs",
/**
* The name of the default branch. Should match one of the keys in {@link DarcsConfig.branches}.
* @example defaultBranch: "main"
* */
defaultBranch: string,
/** The branches of this repository to generate pages for. Since darcs doesn't have
* branches in the same way that other VCSs do, a "branch" in this case is an entirely
* separate repository. So, these "branches" are more like repos that are grouped under
* the same project name. That is why you need to specify a separate location for each
* branch.
* @example
* branches: {
* main: {
* location: "/home/alice/projects/my-project/main",
* description: "The main release branch of this project."
* },
* drafts: {
* location: "/home/alice/projects/my-project/drafts",
* description: "Undeployed works-in-progress for this project."
* }
* }
*/
branches: {
/**
* Each key in this object will be the name that is used for that branch.
*/
[branchName: string]: {
/**
* The absolute path to the repository for this branch.
* @example location: "/home/alice/projects/darcs_repo_main"
*/
location: string,
/**
* A description of this branch. You may want to clarify what this branch is used for here.
*/
description?: string,
}
},
/**
* If your repository has any uncommon file extensions that should be treated like a different
* type of file, list them here. If you include `{njk: "html"}` here, that will tell the
* syntax highlighter to highlight an `njk` file like an `html` file. The key is the file
* extension in your code, and the value is the file extension that the syntax highlighter
* will know about.
* @example
* languageExtensions: {
* njk: "html",
* jss: "js",
* madeupformat: "txt"
* }
*/
languageExtensions?: {
[fileExtension: string]: string
},
artifactSteps?: {
command: string,
copyFrom: string,
copyTo: string,
}[]
}
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
/** @hidden */
const getDarcsDiffsFromPatchText = (patchText: string): Array<DiffInfo> => {
const lines = patchText.split("\n")
const hunks: Array<Hunk> = []
let previousHunk = -1
lines.forEach((line, index) => {
if (line.startsWith("hunk") || index === lines.length - 1) {
if (previousHunk === -1) {
previousHunk = index
return
}
// get diff from previous hunk to this next one
const lastHunk = lines.slice(previousHunk, index + 1) // slice is non-inclusive for the end argument
let lastHunkBefore = lastHunk.filter(line => line.startsWith("-")).map(str => str.replace("-", "")).join("\n")
let lastHunkAfter = lastHunk.filter(line => line.startsWith("+")).map(str => str.replace("+", "")).join("\n")
lastHunkBefore = _.escape(lastHunkBefore)
lastHunkAfter = _.escape(lastHunkAfter)
let filename = lines[previousHunk].replace("hunk ./", "")
const changeObject = Diff.diffWords(lastHunkBefore, lastHunkAfter)
let previousText = ""
let afterText = ""
changeObject.forEach((obj) => {
if (!obj.added && !obj.removed) {
previousText = previousText + obj.value
afterText = afterText + obj.value
}
if (obj.added) {
afterText = afterText + "<mark>" + obj.value + "</mark>"
}
if (obj.removed) {
previousText = previousText + "<mark>" + obj.value + "</mark>"
}
})
const regex = RegExp(/(.*) ([0-9]+)$/)
const matches = filename.match(regex)
const file = matches[1]
const lineNumber: number = parseInt(matches[2])
hunks.push({
file,
lineNumber,
previousText,
afterText,
})
previousHunk = index
}
})
return hunks
}
if (config._type === "darcs") {
return config.branches[branchName].location
}
else if (config._type === "git") {
return config.location
}
getDarcsDiffsFromPatchText,
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
return config.location
1
type DarcsConfig,
1
20 21 22 23 24 25 26
const getBranchNames = (repoConfig: DarcsConfig | GitConfig): Array<string> => {
if (repoConfig._type === 'darcs') {
return Object.keys(repoConfig.branches)
}
else if (repoConfig._type === 'git') {
return repoConfig.branchesToPull
}
20 21 22 23 24 25 26
const getBranchNames = (repoConfig: GitConfig): Array<string> => {
return repoConfig.branchesToPull
1 2 3 4 5
export default {
cloneUrl: (baseUrl: string, repoName: string) => {
return `${baseUrl}/${repoName.toLowerCase().replaceAll(" ", "-")}/branches/`
}
}
1 2 3 4 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
import util from 'util'
import childProcess from 'child_process'
const exec = util.promisify(childProcess.exec)
import {getDarcsDiffsFromPatchText} from '../../helpers.ts'
export const getFileList = async(repoName: string, branchName: string, repoLocation: string) => {
const command = "darcs show files"
const result = await exec(`(cd ${repoLocation} && ${command})`)
const files = result.stdout.split("\n").filter(item => item.length > 0 && item != ".")
return files
}
export const getBranchInfo = async (repoName: string, branchName: string, repoLocation: string) => {
const patches = new Map()
const totalPatchesCountRes = await exec(`(cd ${repoLocation} && darcs log --count)`)
const totalPatchesCount = parseInt(totalPatchesCountRes.stdout)
let hunkRegex = RegExp(/^ *hunk /)
// Get 100 patches at a time and parse those
for (let i = 1; i <= totalPatchesCount; i = i + 100) {
let patchesSubsetRes = await exec(`(cd ${repoLocation} && darcs log --index=${i}-${i+100} -v)`)
let patchesSubset = patchesSubsetRes.stdout.split("\n")
do {
const nextPatchStart = patchesSubset.findIndex((line, index) => {
return (index > 0 && line.startsWith("patch ")) || index === patchesSubset.length - 1
})
const isEndOfFile = nextPatchStart === patchesSubset.length - 1
const currentPatch = patchesSubset.slice(0, isEndOfFile ? nextPatchStart : nextPatchStart - 1)
patchesSubset = patchesSubset.slice(nextPatchStart)
const hash = currentPatch[0].replace("patch ", "").trim()
const author = currentPatch[1].replace("Author: ", "").trim()
const date = currentPatch[2].replace("Date: ", "").trim()
const name = currentPatch[3].replace(" * ", "").trim()
const diffStart = currentPatch.findIndex((line) => {
return line.match(hunkRegex)
})
const description = currentPatch.slice(5, diffStart).map(str => str.replace(" ", "")).join("\n").trim()
const diffs = getDarcsDiffsFromPatchText(currentPatch.slice(diffStart).map(str => str.trimStart()).join("\n"))
patches.set(hash, {
name,
description,
author,
date,
hash,
diffs,
})
} while (patchesSubset.length > 1)
}
return Array.from(patches.values())
}
export const getFileLastTouchInfo = async (repoName: string, branchName: string, filename: string, repoLocation: string) => {
const command = `darcs annotate --machine-readable ${filename}`
const res = await exec(`(cd ${repoLocation} && ${command})`)
const output = res.stdout
const outputLines = output.split("\n").map((line) => {
return line.split(' ')[0]
})
return outputLines.map((line) => {
return {sha: line, author: ''}
})
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
1 2
import darcsHelpers from './darcs/helpers.ts'
darcs: darcsHelpers
1 2
3 4 5 6 7
import {
getBranchInfo as getDarcsBranchInfo,
getFileList as getDarcsFileList,
getFileLastTouchInfo as getDarcsFileLastTouchInfo,
} from './darcs/operations.ts'
3 4 5 6 7
24 25 26 27 28
darcs: {
getBranchInfo: getDarcsBranchInfo,
getFileList: getDarcsFileList,
getFileLastTouchInfo: getDarcsFileLastTouchInfo,
}
24 25 26 27 28
10 11 12 13 14 15 16 17 18
{% if reposConfig.repos[patchInfo.repoName]._type == "darcs" %}
<div class="input-group mb-3 d-flex flex-nowrap">
<span id="clone-command" class="clone input-group-text overflow-hidden">
{% set url = [reposConfig.baseUrl, reposPath, "/", patchInfo.repoName | slugify, "/branches/", patchInfo.branchName | slugify] | join | url %}
darcs pull {{ url }} -h {{patchInfo.patch.hash}}
</span>
<button class="btn btn-primary" id="clone-button" onclick="copyCommand()">Copy</button>
</div>
{% endif %}
10 11 12 13 14 15 16 17 18
24 25 26 27 28 29
{% if reposConfig.repos[branch.repoName]._type == "darcs" %}
<button data-hash="{{patch.hash}}" data-vcs="darcs" class="copy-btn btn btn-sm btn-outline-primary ms-2">
<i class="bi-copy bi me-1"></i>darcs pull {{patch.hash | truncate(6, true, "")}}
</button>
{% elif reposConfig.repos[branch.repoName]._type == "git" %}
{% endif %}
24 25 26 27 28 29