|
|
@ -1,197 +0,0 @@ |
|
|
|
import type { Container } from '../../container'; |
|
|
|
import { filterMap } from '../../system/array'; |
|
|
|
import { debug } from '../../system/decorators/log'; |
|
|
|
import { normalizePath } from '../../system/path'; |
|
|
|
import { getLines } from '../../system/string'; |
|
|
|
import { |
|
|
|
GitCommit, |
|
|
|
GitCommitIdentity, |
|
|
|
GitFile, |
|
|
|
GitFileChange, |
|
|
|
GitFileIndexStatus, |
|
|
|
GitStash, |
|
|
|
GitStashCommit, |
|
|
|
} from '../models'; |
|
|
|
import { fileStatusRegex } from './logParser'; |
|
|
|
// import { Logger } from './logger';
|
|
|
|
|
|
|
|
// Using %x00 codes because some shells seem to try to expand things if not
|
|
|
|
const lb = '%x3c'; // `%x${'<'.charCodeAt(0).toString(16)}`;
|
|
|
|
const rb = '%x3e'; // `%x${'>'.charCodeAt(0).toString(16)}`;
|
|
|
|
const sl = '%x2f'; // `%x${'/'.charCodeAt(0).toString(16)}`;
|
|
|
|
const sp = '%x20'; // `%x${' '.charCodeAt(0).toString(16)}`;
|
|
|
|
|
|
|
|
interface StashEntry { |
|
|
|
ref?: string; |
|
|
|
date?: string; |
|
|
|
committedDate?: string; |
|
|
|
fileNames?: string; |
|
|
|
files?: GitFile[]; |
|
|
|
summary?: string; |
|
|
|
stashName?: string; |
|
|
|
} |
|
|
|
|
|
|
|
export class GitStashParser { |
|
|
|
static defaultFormat = [ |
|
|
|
`${lb}${sl}f${rb}`, |
|
|
|
`${lb}r${rb}${sp}%H`, // ref
|
|
|
|
`${lb}d${rb}${sp}%at`, // date
|
|
|
|
`${lb}c${rb}${sp}%ct`, // committed date
|
|
|
|
`${lb}l${rb}${sp}%gd`, // reflog-selector
|
|
|
|
`${lb}s${rb}`, |
|
|
|
'%B', // summary
|
|
|
|
`${lb}${sl}s${rb}`, |
|
|
|
`${lb}f${rb}`, |
|
|
|
].join('%n'); |
|
|
|
|
|
|
|
@debug({ args: false, singleLine: true }) |
|
|
|
static parse(container: Container, data: string, repoPath: string): GitStash | undefined { |
|
|
|
if (!data) return undefined; |
|
|
|
|
|
|
|
const lines = getLines(`${data}</f>`); |
|
|
|
// Skip the first line since it will always be </f>
|
|
|
|
let next = lines.next(); |
|
|
|
if (next.done) return undefined; |
|
|
|
|
|
|
|
if (repoPath !== undefined) { |
|
|
|
repoPath = normalizePath(repoPath); |
|
|
|
} |
|
|
|
|
|
|
|
const commits = new Map<string, GitStashCommit>(); |
|
|
|
|
|
|
|
let entry: StashEntry = {}; |
|
|
|
let line: string | undefined = undefined; |
|
|
|
let token: number; |
|
|
|
|
|
|
|
let match; |
|
|
|
let renamedFileName; |
|
|
|
|
|
|
|
while (true) { |
|
|
|
next = lines.next(); |
|
|
|
if (next.done) break; |
|
|
|
|
|
|
|
line = next.value; |
|
|
|
|
|
|
|
// <<1-char token>> <data>
|
|
|
|
// e.g. <r> bd1452a2dc
|
|
|
|
token = line.charCodeAt(1); |
|
|
|
|
|
|
|
switch (token) { |
|
|
|
case 114: // 'r': // ref
|
|
|
|
entry = { |
|
|
|
ref: line.substring(4), |
|
|
|
}; |
|
|
|
break; |
|
|
|
|
|
|
|
case 100: // 'd': // author-date
|
|
|
|
entry.date = line.substring(4); |
|
|
|
break; |
|
|
|
|
|
|
|
case 99: // 'c': // committer-date
|
|
|
|
entry.committedDate = line.substring(4); |
|
|
|
break; |
|
|
|
|
|
|
|
case 108: // 'l': // reflog-selector
|
|
|
|
entry.stashName = line.substring(4); |
|
|
|
break; |
|
|
|
|
|
|
|
case 115: // 's': // summary
|
|
|
|
while (true) { |
|
|
|
next = lines.next(); |
|
|
|
if (next.done) break; |
|
|
|
|
|
|
|
line = next.value; |
|
|
|
if (line === '</s>') break; |
|
|
|
|
|
|
|
if (entry.summary === undefined) { |
|
|
|
entry.summary = line; |
|
|
|
} else { |
|
|
|
entry.summary += `\n${line}`; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Remove the trailing newline
|
|
|
|
if (entry.summary != null && entry.summary.charCodeAt(entry.summary.length - 1) === 10) { |
|
|
|
entry.summary = entry.summary.slice(0, -1); |
|
|
|
} |
|
|
|
break; |
|
|
|
|
|
|
|
case 102: // 'f': // files
|
|
|
|
// Skip the blank line git adds before the files
|
|
|
|
next = lines.next(); |
|
|
|
if (!next.done && next.value !== '</f>') { |
|
|
|
while (true) { |
|
|
|
next = lines.next(); |
|
|
|
if (next.done) break; |
|
|
|
|
|
|
|
line = next.value; |
|
|
|
if (line === '</f>') break; |
|
|
|
|
|
|
|
if (line.startsWith('warning:')) continue; |
|
|
|
|
|
|
|
match = fileStatusRegex.exec(line); |
|
|
|
if (match != null) { |
|
|
|
if (entry.files === undefined) { |
|
|
|
entry.files = []; |
|
|
|
} |
|
|
|
|
|
|
|
renamedFileName = match[3]; |
|
|
|
if (renamedFileName !== undefined) { |
|
|
|
entry.files.push({ |
|
|
|
status: match[1] as GitFileIndexStatus, |
|
|
|
path: renamedFileName, |
|
|
|
originalPath: match[2], |
|
|
|
}); |
|
|
|
} else { |
|
|
|
entry.files.push({ |
|
|
|
status: match[1] as GitFileIndexStatus, |
|
|
|
path: match[2], |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (entry.files != null) { |
|
|
|
entry.fileNames = filterMap(entry.files, f => (f.path ? f.path : undefined)).join(', '); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
GitStashParser.parseEntry(container, entry, repoPath, commits); |
|
|
|
entry = {}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const stash: GitStash = { |
|
|
|
repoPath: repoPath, |
|
|
|
commits: commits, |
|
|
|
}; |
|
|
|
return stash; |
|
|
|
} |
|
|
|
|
|
|
|
private static parseEntry( |
|
|
|
container: Container, |
|
|
|
entry: StashEntry, |
|
|
|
repoPath: string, |
|
|
|
commits: Map<string, GitStashCommit>, |
|
|
|
) { |
|
|
|
let commit = commits.get(entry.ref!); |
|
|
|
if (commit == null) { |
|
|
|
commit = new GitCommit( |
|
|
|
container, |
|
|
|
repoPath, |
|
|
|
entry.ref!, |
|
|
|
new GitCommitIdentity('You', undefined, new Date((entry.date! as any) * 1000)), |
|
|
|
new GitCommitIdentity('You', undefined, new Date((entry.committedDate! as any) * 1000)), |
|
|
|
entry.summary?.split('\n', 1)[0] ?? '', |
|
|
|
[], |
|
|
|
entry.summary ?? '', |
|
|
|
entry.files?.map(f => new GitFileChange(repoPath, f.path, f.status, f.originalPath)) ?? [], |
|
|
|
undefined, |
|
|
|
[], |
|
|
|
entry.stashName, |
|
|
|
) as GitStashCommit; |
|
|
|
} |
|
|
|
|
|
|
|
commits.set(entry.ref!, commit); |
|
|
|
} |
|
|
|
} |