Skip to content

Commit

Permalink
Add LFS object data get using git lfs smudge. Blob images for desktop…
Browse files Browse the repository at this point in the history
…#2981 enchancement.
  • Loading branch information
Simeon Petrov committed Feb 26, 2024
1 parent 5c49441 commit 9fc466b
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 1 deletion.
3 changes: 3 additions & 0 deletions app/src/lib/git/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export interface IGitExecutionOptions extends DugiteExecutionOptions {

/** Should it track & report LFS progress? */
readonly trackLFSProgress?: boolean

/** Pass stdin to executed command */
readonly stdin?: string | Buffer
}

/**
Expand Down
104 changes: 103 additions & 1 deletion app/src/lib/git/diff.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Path from 'path'

import { getBlobContents } from './show'
import { getBlobContents, getLFSBlobContents } from './show'

import { Repository } from '../../models/repository'
import {
Expand All @@ -12,6 +12,8 @@ import {
} from '../../models/status'
import {
DiffType,
DiffHunk,
DiffLineType,
IRawDiff,
IDiff,
IImageDiff,
Expand Down Expand Up @@ -98,6 +100,11 @@ const imageFileExtensions = new Set([
'.avif',
])

/**
* Defining the LFS version string
*/
const LFSVersionString: string = "version https://git-lfs.github.com/spec/v1"

/**
* Render the difference between a file in the given commit and its parent
*
Expand Down Expand Up @@ -467,6 +474,64 @@ async function getImageDiff(
}
}

async function getLFSImageDiff(
repository: Repository,
file: FileChange,
hunk: DiffHunk
): Promise<IImageDiff> {
let current: Image | undefined = undefined
let previous: Image | undefined = undefined

let previousStr: string = ""
let currentStr: string = ""

for (let i = 0; i < hunk.lines.length; i++) {
const element = hunk.lines[i];
if (element.type == DiffLineType.Delete) {
let delIdx = element.text.indexOf('-');
if (delIdx > -1) {
delIdx += '-'.length;
previousStr += element.text.substring(delIdx) + '\n';
}
}

if (element.type == DiffLineType.Add) {
let addIdx = element.text.indexOf('+');
if (addIdx > -1) {
addIdx += '+'.length;
currentStr += element.text.substring(addIdx) + '\n';
}
}
}

if (previousStr.length > 0 && currentStr.length > 0) {
previousStr = LFSVersionString + '\n' + previousStr;
currentStr = LFSVersionString + '\n' + currentStr;
}

if (previousStr.length > 0) {
previous = await getLFSBlobImage(
repository,
getOldPathOrDefault(file),
previousStr
)
}

if (currentStr.length > 0) {
current = await getLFSBlobImage(
repository,
getOldPathOrDefault(file),
currentStr
)
}

return {
kind: DiffType.Image,
previous: previous,
current: current,
}
}

export async function convertDiff(
repository: Repository,
file: FileChange,
Expand All @@ -487,6 +552,19 @@ export async function convertDiff(
}
}

if (diff.hunks.length > 0) {
const hunk = diff.hunks[0];
if (hunk.lines.length > 1) {
const line = hunk.lines[1];
// search for LFS string in first second line of diff
if (line.text.indexOf(LFSVersionString) > -1) {
if (imageFileExtensions.has(extension)) {
return getLFSImageDiff(repository, file, hunk);
}
}
}
}

return {
kind: DiffType.Text,
text: diff.contents,
Expand Down Expand Up @@ -684,6 +762,30 @@ export async function getBlobImage(
contents.length
)
}

/**
* Retrieve the binary contents of a blob from the LFS object database
*
* Returns an image object containing the base64 encoded string,
* as <img> tags support the data URI scheme instead of
* needing to reference a file:// URI
*
* https://en.wikipedia.org/wiki/Data_URI_scheme
*/
export async function getLFSBlobImage(
repository: Repository,
path: string,
LFSMetadata: string
): Promise<Image> {
const extension = Path.extname(path)
const contents = await getLFSBlobContents(repository, LFSMetadata)
return new Image(
contents.toString('base64'),
getMediaType(extension),
contents.length
)
}

/**
* Retrieve the binary contents of a blob from the working directory
*
Expand Down
37 changes: 37 additions & 0 deletions app/src/lib/git/show.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,43 @@ export async function getBlobContents(
return Buffer.from(blobContents.stdout, 'binary')
}

/**
* Retrieve the binary contents of a blob from the repository at a given
* reference, commit, or tree.
*
* Returns a promise that will produce a Buffer instance containing
* the binary contents of the blob or an error if the file doesn't
* exists in the given revision.
*
* @param repository - The repository from where to read the blob
*
* @param LFSMetadata - LFS Object metadata containing lfs version, noid sha256 and size.
*/
export async function getLFSBlobContents(
repository: Repository,
LFSMetadata: string
): Promise<Buffer> {
const successExitCodes = new Set([0, 1])
const setBinaryEncoding: (process: ChildProcess) => void = cb => {
// If Node.js encounters a synchronous runtime error while spawning
// `stdout` will be undefined and the error will be emitted asynchronously
if (cb.stdout) {
cb.stdout.setEncoding('binary')
}
}

const args = ['lfs', 'smudge']
const opts = {
successExitCodes,
processCallback: setBinaryEncoding,
stdin: Buffer.from(LFSMetadata, "binary")
}

const blobContents = await git(args, repository.path, 'getLFSBlobContents', opts)

return Buffer.from(blobContents.stdout, 'binary')
}

/**
* Retrieve some or all binary contents of a blob from the repository
* at a given reference, commit, or tree. This is almost identical
Expand Down

0 comments on commit 9fc466b

Please sign in to comment.