import os, times, uri, strutils, sequtils import nimgit, markdown import types, utils type CommitSummary = object id: string shortId: string `when`: string committerIsAuthor: bool authorName: string authorMail: string committerName: string committerMail: string summary: string body: string message: string RepoLog = object description: string category: string commits: seq[CommitSummary] RepoTreeEntry = object isDir: bool name: string nameUrl: string mode: string size: string PathObj = object name: string url: string Line = object `type`: string prefix: string content: string Hunk = object header: string lines: seq[Line] Patch = object status: string newFile: string newFileUrl: string oldFile: string oldFileUrl: string hunks: seq[Hunk] Blob = object path: seq[PathObj] id: string filename: string filenameUrl: string filesize: string isBinary: bool lines: seq[int] content: string include "templates/repoSummary.nimf" include "templates/repoTree.nimf" include "templates/repoBlob.nimf" include "templates/repoLog.nimf" include "templates/repoCommit.nimf" include "templates/repoRefs.nimf" proc createCommitSummary (commit: GitCommit): CommitSummary = let author = commit.author let committer = commit.committer result.id = $commit.id result.shortId = commit.shortId result.when = commit.time.time.format("yyyy'.'MM'.'dd HH:mm") result.committerIsAuthor = (author.email == committer.email) result.authorName = author.name result.authorMail = author.email result.committerName = committer.name result.committerMail = committer.email result.summary = commit.summary result.body = commit.body result.message = commit.message proc generateCommitPage (path: string, templateContext: TemplateContext, commit: GitCommit) = if fileExists(joinPath(joinPath(path, $commit.id & ".html"))): return let id = $commit.id author = commit.author committer = commit.committer var parents : seq[string] let commitSummary = createCommitSummary(commit) if commit.parentCount != 0: for id in commit.parentIds: parents.add($id) let repo = commit.owner let tree = commit.tree let diffopts = initDiffOptions() diffopts.flags = cast[uint32](GIT_DIFF_DISABLE_PATHSPEC_MATCH) or cast[uint32](GIT_DIFF_IGNORE_SUBMODULES) or cast[uint32](GIT_DIFF_INCLUDE_TYPECHANGE) let findopts = GitDiffFindOptions() findopts.flags = cast[uint32](GIT_DIFF_FIND_RENAMES) or cast[uint32](GIT_DIFF_FIND_COPIES) or cast[uint32](GIT_DIFF_FIND_EXACT_MATCH_ONLY) var diff: GitDiff if commit.parentCount != 0: let parent = repo.lookupCommit(commit.parentIds[0]) let parentTree = repo.lookupTree(parent.treeId) diff = repo.diffTrees(parentTree, tree, diffopts) free(parentTree) free(parent) else: let treeId = fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904") let parentTree = repo.lookupTree(treeId) diff = repo.diffTrees(parentTree, tree, diffopts) free(parentTree) free(treeId) free(tree) diff.findSimilar(findopts) let diffStats = diff.stats var patches: seq[Patch] for deltaIndex, delta in diff.deltas: let patch = diff.patch(deltaIndex) var hunks : seq[Hunk] for hunkIndex, hunk in patch.hunks(): var lines: seq[Line] for lineIndex, line in patch.lines(hunkIndex): var content = newString(line.content_len) copyMem(content.cstring, line.content, line.content_len) var tLine = Line(content: content) if line.old_lineno == -1: tLine.type = "insertion" tLine.prefix = "+" elif line.new_lineno == -1: tLine.type = "deletion" tLine.prefix = "-" else: tLine.prefix = " " lines.add(tLine) var header = newString(hunk.header_len) copyMem(header.cstring, hunk.header[0].unsafeAddr, hunk.header_len) hunks.add(Hunk( header: header, lines: lines )) patches.add(Patch( status: delta.statusChar, newFile: $delta.new_file.path, newFileUrl: encodeUrl($delta.new_file.path), oldFile: $delta.old_file.path, oldFileUrl: encodeUrl($delta.old_file.path), hunks: hunks )) free(patch) free(diff) writeFile( joinPath(joinPath(path, id & ".html")), templateCommitPage(templateContext, commitSummary, parents, diffStats, patches) ) proc generateRepoBlobPage (path: string, pathSeq: seq[PathObj], templateContext: TemplateContext, entry: GitTreeEntry, blob: GitBlob) = let content = blob.content var blobData = Blob( path: pathSeq, id: $entry.id, filename: entry.name, filenameUrl: encodeUrl(entry.name), filesize: formatSize(blob.size), isBinary: blob.isBinary ) if not blob.isBinary: blobData.content = content blobData.lines = toSeq(1..content.countLines) writeFile(joinPath(path, entry.name), content) writeFile( joinPath(joinPath(path, entry.name & ".html")), templateBlobPage(templateContext, pathSeq, blobData) ) proc generateRepoTreePage (isSubdir: bool, path: string, pathSeq: seq[PathObj], dirName: string, templateContext: TemplateContext, tree: GitTree) = discard existsOrCreateDir(joinPath(path)) var entries : seq[RepoTreeEntry] tempPathSeq = pathSeq if isSubdir: tempPathSeq.add(PathObj( name: dirName, url: pathSeq[pathSeq.len-1].url & "/" & encodeUrl(dirName) )) for entry in tree: var size: int if entry.type == goBlob: let blob = tree.owner.lookupBlob(entry.id) tempPathSeq.add(PathObj( name: dirName, url: tempPathSeq[tempPathSeq.len-1].url & "/" & encodeUrl(dirName) )) tempPathSeq.delete(tempPathSeq.len-1) generateRepoBlobPage(path, tempPathSeq, templateContext, entry, blob) size = blob.size free(blob) entries.add(RepoTreeEntry( isDir: (entry.type != goBlob), name: entry.name, nameUrl: encodeUrl(entry.name), mode: entry.modeStr, size: formatSize(size) )) if entry.type == goTree: let subtree = tree.owner.lookupTree(entry.id) generateRepoTreePage(true, joinPath(path, entry.name), tempPathSeq, entry.name, templateContext, subtree) free(subtree) free(entry) writeFile( joinPath(path, "index.html"), templateTreePage( templateContext, dirName, isSubdir, pathSeq, entries ) ) proc repoGenerator* (config: Config, name: string) = var repository: GitRepository repoConfig: GitConfig try: repository = openGitRepository(joinPath(config.scanPath, name)) repoConfig = repository.config let repoName = nameFromPath(name) description = repoConfig.get("gitweb.description") category = repoConfig.get("gitweb.category") templateContext = TemplateContext( siteTitle: config.title, repoName: repoName, repoUrl: encodeUrl(repoName), generated: $now(), description: description, category: category ) echo "Generate pages for repo: " & repoName discard existsOrCreateDir(joinPath(config.outputDirectory, repoName)) discard existsOrCreateDir(joinPath(config.outputDirectory, repoName, "log")) discard existsOrCreateDir(joinPath(config.outputDirectory, repoName, "tree")) discard existsOrCreateDir(joinPath(config.outputDirectory, repoName, "refs")) # # Summary page # let headObjId = repository.lookupObjectIdByName("HEAD") headCommit = repository.lookupCommit(headObjId) tree = headCommit.tree parentCount = headCommit.parentCount var lastCommits: seq[CommitSummary] var readmeContent: string lastCommits.add(createCommitSummary(headCommit)) if parentCount != 0: let parentCommit = repository.lookupCommit(headCommit.parentIds[0]) lastCommits.add(createCommitSummary(parentCommit)) free(parentCommit) for readmeFile in config.readmeFiles: if tree.entries.contains(readmeFile): let entry = tree.entry(readmeFile) blob = repository.lookupBlob(entry.id) if not config.renderMarkdown: readmeContent = blob.content else: readmeContent = markdown(blob.content, config=initGfmConfig()) free(blob) free(entry) break echo "Generate repo-summary page" writeFile( joinPath(config.outputDirectory, repoName, "index.html"), templateSummaryPage(templateContext, lastCommits, readmeContent) ) # # END Summary page # # # Tree pages # let headCacheFile = joinPath(config.outputDirectory, repoName, "tree/.head") if not fileExists(headCacheFile) or readFile(headCacheFile) != $headObjId: writeFile(headCacheFile, $headObjId) var pathSeq: seq[PathObj] pathSeq.add(PathObj( name: "root", url: "/" & encodeUrl(repoName) & "/tree" )) echo "Generate repo-tree and blob pagesp" generateRepoTreePage(false, joinPath(config.outputDirectory, repoName, "tree"), pathSeq, "", templateContext, tree) # # END Tree pages # # # Log page and Commit pages # let gitRevisionWalker = repository.walk() var commits: seq[CommitSummary] gitRevisionWalker.sort(GIT_SORT_TOPOLOGICAL) gitRevisionWalker.pushHead() for objectId in gitRevisionWalker: let commit = repository.lookupCommit(objectId) generateCommitPage(joinPath(config.outputDirectory, repoName, "log"), templateContext, commit) commits.add(createCommitSummary(commit)) free(commit) echo "Generate repo-commit and log page(s)" writeFile( joinPath(config.outputDirectory, repoName, "log/index.html"), templateLogPage(templateContext, commits) ) # # END Log page and Commit pages # # # Log page and Commit pages # echo "Generate repo-refs page!" writeFile( joinPath(config.outputDirectory, repoName, "refs/index.html"), templateRefsPage(templateContext) ) # # END Log page and Commit pages # free(tree) free(headCommit) free(headObjId) free(repoConfig) free(repository) except: free(repoConfig) free(repository) echo "Error:\n", getCurrentExceptionMsg()