or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-operations.mdbranch-operations.mddata-operations.mdfile-operations.mdindex.mdinformation-status.mdremote-operations.mdrepository-management.md
tile.json

data-operations.mddocs/

Data Operations

Operations for working with Git data including diffs and file content retrieval. These are stream-based functions that return Git data as vinyl files or process existing vinyl files.

Capabilities

Git Diff

Gets Git diff results as vinyl file streams using git diff. Parses raw diff output and optionally reads file contents.

/**
 * Gets git diff results as vinyl file streams
 * @param compare - Git diff comparison argument (branch, commit, or tag)
 * @param opt - Configuration options (optional)
 * @returns Stream of vinyl files with git diff information
 */
function diff(compare: string, opt?: DiffOptions): Transform;

interface DiffOptions extends BaseOptions {
  /** Base directory for vinyl files */
  base?: string;
  /** Read file contents via catFile (default: false) */
  read?: boolean;
  /** Buffer file contents (default: true) */
  buffer?: boolean;
  /** Strip BOM from file contents (default: true) */
  stripBOM?: boolean;
  /** Enable logging of diff command */
  log?: boolean;
}

Usage Examples:

const gulp = require('gulp');
const git = require('gulp-git');

// Basic diff against master
gulp.task('diff-master', function() {
  return gulp.src('./*')
    .pipe(git.diff('master'))
    .pipe(gulp.dest('./diff-output'));
});

// Diff with logging enabled
gulp.task('diff-verbose', function() {
  return gulp.src('./*')
    .pipe(git.diff('develop', { log: true }))
    .pipe(gulp.dest('./diff-output'));
});

// Diff with file contents
gulp.task('diff-with-content', function() {
  return gulp.src('./*')
    .pipe(git.diff('HEAD~1', { 
      read: true, 
      buffer: true 
    }))
    .pipe(gulp.dest('./diff-output'));
});

// Diff against specific commit
gulp.task('diff-commit', function() {
  return gulp.src('./*')
    .pipe(git.diff('abc1234', { log: true }))
    .pipe(gulp.dest('./diff-output'));
});

// Diff with custom base directory
gulp.task('diff-custom-base', function() {
  return gulp.src('./src/*')
    .pipe(git.diff('master', { 
      base: './src',
      log: true 
    }))
    .pipe(gulp.dest('./diff-output'));
});

Cat File

Reads Git blob contents using git cat-file for vinyl files. Retrieves file contents from Git object database.

/**
 * Reads git blob contents using git cat-file for vinyl files
 * @param opt - Configuration options (optional)
 * @returns Transform stream that reads blob contents for vinyl files
 */
function catFile(opt?: CatFileOptions): Transform;

interface CatFileOptions {
  /** Strip BOM from file contents (default: true) */
  stripBOM?: boolean;
  /** Buffer file contents (default: true) */
  buffer?: boolean;
}

Usage Examples:

// Read file contents from git objects
// Note: This requires vinyl files with git.hash property set
gulp.task('cat-files', function() {
  return gulp.src('./*')
    .pipe(/* some operation that sets file.git.hash */)
    .pipe(git.catFile())
    .pipe(gulp.dest('./output'));
});

// Read without BOM stripping
gulp.task('cat-files-raw', function() {
  return gulp.src('./*')
    .pipe(/* operation setting git hash */)
    .pipe(git.catFile({ stripBOM: false }))
    .pipe(gulp.dest('./output'));
});

// Read as streams (unbuffered)
gulp.task('cat-files-stream', function() {
  return gulp.src('./*')
    .pipe(/* operation setting git hash */)
    .pipe(git.catFile({ buffer: false }))
    .pipe(gulp.dest('./output'));
});

Common Patterns

Diff Analysis Workflow

const gulp = require('gulp');
const git = require('gulp-git');
const through = require('through2');

gulp.task('analyze-diff', function() {
  let changedFiles = 0;
  let additions = 0;
  let deletions = 0;
  
  return gulp.src('./*')
    .pipe(git.diff('master', { log: true }))
    .pipe(through.obj(function(file, enc, cb) {
      changedFiles++;
      
      // Analyze diff content (simplified)
      if (file.contents) {
        const content = file.contents.toString();
        const lines = content.split('\n');
        
        lines.forEach(line => {
          if (line.startsWith('+') && !line.startsWith('+++')) additions++;
          if (line.startsWith('-') && !line.startsWith('---')) deletions++;
        });
      }
      
      this.push(file);
      cb();
    }))
    .pipe(gulp.dest('./diff-analysis'))
    .on('end', function() {
      console.log(`Analysis complete:`);
      console.log(`Changed files: ${changedFiles}`);
      console.log(`Additions: ${additions}`);
      console.log(`Deletions: ${deletions}`);
    });
});

Compare Branches

gulp.task('compare-branches', function(done) {
  const branch1 = process.env.BRANCH1 || 'master';
  const branch2 = process.env.BRANCH2 || 'develop';
  
  console.log(`Comparing ${branch1}...${branch2}`);
  
  return gulp.src('./*')
    .pipe(git.diff(`${branch1}...${branch2}`, { log: true }))
    .pipe(through.obj(function(file, enc, cb) {
      console.log(`Changed: ${file.relative}`);
      this.push(file);
      cb();
    }))
    .pipe(gulp.dest(`./comparison-${branch1}-${branch2}`));
});

Historical File Content

// This pattern would require additional setup to work with catFile
// as it needs git.hash to be set on vinyl files
function getFileAtCommit(filePath, commit, callback) {
  git.exec({ 
    args: `rev-parse ${commit}:${filePath}` 
  }, function(err, hash) {
    if (err) return callback(err);
    
    // Create vinyl file with git hash
    const file = new (require('vinyl'))({
      path: filePath,
      git: { hash: hash.trim() }
    });
    
    // Use catFile to get content
    const stream = git.catFile();
    
    stream.on('data', function(file) {
      callback(null, file.contents.toString());
    });
    
    stream.on('error', callback);
    stream.write(file);
    stream.end();
  });
}

gulp.task('historical-content', function(done) {
  const filePath = process.env.FILE_PATH || 'README.md';
  const commit = process.env.COMMIT || 'HEAD~5';
  
  getFileAtCommit(filePath, commit, function(err, content) {
    if (err) return done(err);
    
    console.log(`Content of ${filePath} at ${commit}:`);
    console.log(content);
    done();
  });
});

Diff Statistics

function calculateDiffStats(diffContent) {
  const lines = diffContent.split('\n');
  const stats = {
    files: 0,
    insertions: 0,
    deletions: 0,
    binary: 0
  };
  
  let currentFile = null;
  
  lines.forEach(line => {
    if (line.startsWith('diff --git')) {
      stats.files++;
      currentFile = line;
    } else if (line.startsWith('+') && !line.startsWith('+++')) {
      stats.insertions++;
    } else if (line.startsWith('-') && !line.startsWith('---')) {
      stats.deletions++;
    } else if (line.includes('Binary files differ')) {
      stats.binary++;
    }
  });
  
  return stats;
}

gulp.task('diff-stats', function() {
  const stats = {
    totalFiles: 0,
    totalInsertions: 0,
    totalDeletions: 0,
    totalBinary: 0
  };
  
  return gulp.src('./*')
    .pipe(git.diff('master', { log: true }))
    .pipe(through.obj(function(file, enc, cb) {
      if (file.contents) {
        const fileStats = calculateDiffStats(file.contents.toString());
        stats.totalFiles += fileStats.files;
        stats.totalInsertions += fileStats.insertions;
        stats.totalDeletions += fileStats.deletions;
        stats.totalBinary += fileStats.binary;
      }
      
      this.push(file);
      cb();
    }))
    .pipe(gulp.dest('./diff-output'))
    .on('end', function() {
      console.log('Diff Statistics:');
      console.log(`Files changed: ${stats.totalFiles}`);
      console.log(`Insertions: +${stats.totalInsertions}`);
      console.log(`Deletions: -${stats.totalDeletions}`);
      console.log(`Binary files: ${stats.totalBinary}`);
    });
});

Export Diff as Patch

gulp.task('export-patch', function() {
  const patchName = process.env.PATCH_NAME || 'changes.patch';
  const targetBranch = process.env.TARGET_BRANCH || 'master';
  
  return gulp.src('./*')
    .pipe(git.diff(targetBranch, { log: true }))
    .pipe(require('gulp-concat')(patchName))
    .pipe(gulp.dest('./patches'))
    .on('end', function() {
      console.log(`Patch exported as ./patches/${patchName}`);
    });
});

Validate File Changes

gulp.task('validate-changes', function() {
  const allowedExtensions = ['.js', '.json', '.md'];
  let hasInvalidFiles = false;
  
  return gulp.src('./*')
    .pipe(git.diff('master'))
    .pipe(through.obj(function(file, enc, cb) {
      const ext = require('path').extname(file.relative);
      
      if (!allowedExtensions.includes(ext)) {
        console.warn(`Warning: Modified file ${file.relative} has unexpected extension ${ext}`);
        hasInvalidFiles = true;
      }
      
      this.push(file);
      cb();
    }))
    .pipe(gulp.dest('./validated-changes'))
    .on('end', function() {
      if (hasInvalidFiles) {
        console.log('❌ Some files have unexpected extensions');
      } else {
        console.log('✅ All changed files have valid extensions');
      }
    });
});

Integration with Other Operations

Diff and Commit Workflow

gulp.task('review-and-commit', function(done) {
  // First show what's different
  const diffStream = gulp.src('./*')
    .pipe(git.diff('HEAD', { log: true }))
    .pipe(gulp.dest('./review'));
  
  diffStream.on('end', function() {
    console.log('Diff saved to ./review directory');
    
    // Prompt user or automatically commit
    gulp.src('./*')
      .pipe(git.add())
      .pipe(git.commit('Auto-commit after review'))
      .on('end', function() {
        console.log('Changes committed');
        done();
      });
  });
});