git2dart 0.1.0 copy "git2dart: ^0.1.0" to clipboard
git2dart: ^0.1.0 copied to clipboard

Dart bindings to libgit2, provides ability to use libgit2 library in Dart and Flutter.

example/example.dart

import 'dart:io';

import 'package:git2dart/git2dart.dart';
import 'package:path/path.dart' as path;

import '../test/helpers/util.dart';

// These examples are basic emulation of core Git CLI functions demonstrating
// basic git2dart API usage. Be advised that they don't have error handling,
// copy with caution.

void main() {
  // Prepare empty directory for repository.
  final tmpDir = Directory.systemTemp.createTempSync('example_repo');

  // Initialize a repository.
  final repo = initRepo(tmpDir.path);

  // Setup user name and email.
  setupNameAndEmail(repo);

  // Stage untracked file.
  const fileName = 'file.txt';
  final filePath = path.join(repo.workdir, fileName);
  File(filePath).createSync();
  stageUntracked(repo: repo, filePath: fileName);

  // Stage modified file.
  File(filePath).writeAsStringSync('some edit\n');
  stageModified(repo: repo, filePath: fileName);

  // Check repository status.
  repoStatus(repo);

  // Commit changes.
  commitChanges(repo, fileName);

  // View commit history.
  viewHistory(repo);

  // View a particular commit.
  viewCommit(repo);

  // View changes before commiting.
  File(filePath).writeAsStringSync('some changes\n');
  viewChanges(repo);

  // Discard staged changes.
  stageModified(repo: repo, filePath: fileName);
  discardStaged(repo: repo, filePath: fileName);

  // Discard changes in the working directory.
  discardNotStaged(repo: repo, filePath: fileName);

  // Remove tracked file from the index and current working tree.
  removeFile(repo: repo, filePath: fileName);

  // Amend the most recent commit.
  amendCommit(repo);

  // Create and switch to a new local branch.
  createAndSwitchToBranch(repo);

  // List all branches.
  listBranches(repo);

  // Merge two branches.
  mergeBranches(repo);

  // Delete a branch.
  deleteBranch(repo);

  // Add a remote repository.
  addRemote(repo);

  // View remote URLs.
  viewRemoteUrls(repo);

  // Remove a remote repository.
  removeRemote(repo);

  // Pull changes from a remote repository.
  pullChanges(repo);

  // Push changes to a remote repository.
  pushChanges(repo);

  // Push a new branch to remote repository.
  pushNewBranch(repo);

  // Clean up.
  tmpDir.deleteSync(recursive: true);
}

/// Initialize a repository at provided path.
///
/// Similar to `git init`.
Repository initRepo(String path) {
  final repo = Repository.init(path: path);
  stdout.writeln('Initialized empty Git repository in ${repo.path}');
  return repo;
}

/// Setup user name and email.
///
/// Similar to:
/// - `git config --add user.name "User Name"`
/// - `git config --add user.email "user@email.com"`
void setupNameAndEmail(Repository repo) {
  final config = repo.config;
  config['user.name'] = 'User Name';
  config['user.email'] = 'user@email.com';
  stdout.writeln('\nSetup user name and email:');
  stdout.writeln('user.name=${config['user.name'].value}');
  stdout.writeln('user.email=${config['user.email'].value}');
}

/// Stage untracked file.
///
/// Similar to `git add file.txt`
void stageUntracked({required Repository repo, required String filePath}) {
  final index = repo.index;
  index.add(filePath);
  index.write();
  stdout.writeln('\nStaged previously untracked file $filePath');
}

/// Stage modified file.
///
/// Similar to `git add file.txt`
void stageModified({required Repository repo, required String filePath}) {
  final index = repo.index;
  index.updateAll([filePath]);
  index.write();
  stdout.writeln('\nChanges to $filePath were staged');
}

/// Check repository status.
///
/// Similar to `git status`
void repoStatus(Repository repo) {
  stdout.writeln('\nChanges to be committed:');
  for (final file in repo.status.entries) {
    if (file.value.contains(GitStatus.indexNew)) {
      stdout.writeln('\tnew file: \t${file.key}');
    }
    if (file.value.contains(GitStatus.indexModified)) {
      stdout.writeln('\tmodified: \t${file.key}');
    }
  }
}

/// Commit changes.
///
/// Similar to `git commit -m "initial commit"`
void commitChanges(Repository repo, String fileName) {
  final signature = repo.defaultSignature;
  const commitMessage = 'initial commit\n';

  repo.index.write();

  final oid = repo.createCommitOnHead(
    [fileName],
    signature,
    signature,
    commitMessage,
  );

  stdout.writeln(
    '\n[${repo.head.shorthand} (root-commit) ${oid.sha.substring(0, 7)}] '
    '$commitMessage',
  );
}

/// View commit history.
///
/// Similar to `git log`
void viewHistory(Repository repo) {
  final commits = repo.log(oid: repo.head.target);

  for (final commit in commits) {
    stdout.writeln('\ncommit ${commit.oid.sha}');
    stdout.writeln('Author: ${commit.author.name} <${commit.author.email}>');
    stdout.writeln(
      'Date:   ${DateTime.fromMillisecondsSinceEpoch(commit.time * 1000)} '
      '${commit.timeOffset}',
    );
    stdout.writeln('\n\t${commit.message}');
  }
}

/// View a particular commit.
///
/// Similar to `git show aaf8f1e`
void viewCommit(Repository repo) {
  final commit = Commit.lookup(repo: repo, oid: repo.head.target);

  stdout.writeln('\ncommit ${commit.oid.sha}');
  stdout.writeln('Author: ${commit.author.name} <${commit.author.email}>');
  stdout.writeln(
    'Date:   ${DateTime.fromMillisecondsSinceEpoch(commit.time * 1000)} '
    '${commit.timeOffset}',
  );
  stdout.writeln('\n\t${commit.message}');

  final diff = Diff.treeToTree(
    repo: repo,
    oldTree: null,
    newTree: commit.tree,
  );
  stdout.writeln('\n${diff.patch}');
}

/// View changes before commiting.
///
/// Similar to `git diff`
void viewChanges(Repository repo) {
  final diff = Diff.indexToWorkdir(repo: repo, index: repo.index);
  stdout.writeln('\n${diff.patch}');
}

/// Discard staged changes.
///
/// Similar to `git restore --staged file.txt`
void discardStaged({required Repository repo, required String filePath}) {
  repo.resetDefault(oid: repo.head.target, pathspec: [filePath]);
  stdout.writeln('Staged changes to $filePath were discarded');
}

/// Discard changes in the working directory.
///
/// Similar to `git restore file.txt`
void discardNotStaged({required Repository repo, required String filePath}) {
  final patch = Patch.fromBuffers(
    // Current content of modified file.
    oldBuffer: File(path.join(repo.workdir, filePath)).readAsStringSync(),
    oldBufferPath: filePath,
    // Old content of file as found in previously written blob.
    newBuffer: Blob.lookup(repo: repo, oid: repo.index[filePath].oid).content,
    newBufferPath: filePath,
  );

  // Apply a diff to the repository, making changes in working directory.
  Diff.parse(patch.text).apply(repo: repo);
  stdout.writeln('\nChanges to $filePath in working directory were discarded.');
}

/// Remove tracked files from the index and current working tree.
///
/// Similar to `git rm file.txt`
void removeFile({required Repository repo, required String filePath}) {
  File(path.join(repo.workdir, filePath)).deleteSync();
  repo.index.updateAll([filePath]);
  stdout.writeln('\nrm $filePath');
}

/// Amend the most recent commit.
///
/// Similar to `git commit --amend -m "Updated message for the previous commit"`
void amendCommit(Repository repo) {
  const commitMessage = "Updated message for the previous commit\n";

  repo.index.write();

  final oid = Commit.amend(
    repo: repo,
    commit: Commit.lookup(repo: repo, oid: repo.head.target),
    updateRef: 'HEAD',
    message: commitMessage,
    tree: Tree.lookup(repo: repo, oid: repo.index.writeTree()),
  );

  stdout.writeln(
    '\n[${repo.head.shorthand} ${oid.sha.substring(0, 7)}] '
    '$commitMessage',
  );
}

/// Create and switch to a new local branch.
///
/// Similar to `git checkout -b new-branch`
void createAndSwitchToBranch(Repository repo) {
  final branch = Branch.create(
    repo: repo,
    name: 'new-branch',
    target: Commit.lookup(repo: repo, oid: repo.head.target),
  );
  final fullName = 'refs/heads/${branch.name}';
  Checkout.reference(repo: repo, name: fullName);
  repo.setHead(fullName);
  stdout.writeln('Switched to a new branch "${repo.head.name}"');
}

/// List all branches.
///
/// Similar to `git branch -a`
void listBranches(Repository repo) {
  stdout.writeln();

  final branches = repo.branches;
  for (final branch in branches) {
    stdout.writeln(
      repo.head.shorthand == branch.name
          ? '* ${branch.name}'
          : '  ${branch.name}',
    );
  }
}

/// Merge two branches.
///
/// Example shows the simplest fast-forward merge. In reality you could find
/// the base between commits with [Merge.base], perform analysis for merge with
/// [Merge.analysis] and based on result invoke further needed methods.
///
/// Similar to `git merge new-branch`
void mergeBranches(Repository repo) {
  // Making changes on 'new-branch'
  File(path.join(repo.workdir, 'new_branch_file.txt')).createSync();

  // Committing on 'new-branch'
  final signature = repo.defaultSignature;
  repo.index.write();
  final newBranchOid = Commit.create(
    repo: repo,
    updateRef: 'HEAD',
    author: signature,
    committer: signature,
    message: 'commit on new-branch\n',
    tree: Tree.lookup(repo: repo, oid: repo.index.writeTree()),
    parents: [Commit.lookup(repo: repo, oid: repo.head.target)],
  );

  // Switching to 'master'
  Checkout.reference(repo: repo, name: 'refs/heads/master');
  repo.setHead('refs/heads/master');

  // Merging commit into HEAD and writing results into the working directory.
  // Repository is put into a merging state.
  Merge.commit(
    repo: repo,
    commit: AnnotatedCommit.lookup(repo: repo, oid: newBranchOid),
  );

  // Staging merged files.
  repo.index.addAll(repo.status.keys.toList());

  // Committing on 'master'
  repo.index.write();
  final parent = Commit.lookup(repo: repo, oid: repo.head.target);
  final masterOid = Commit.create(
    repo: repo,
    updateRef: 'HEAD',
    author: signature,
    committer: signature,
    message: 'commit on new-branch\n',
    tree: Tree.lookup(repo: repo, oid: repo.index.writeTree()),
    parents: [parent],
  );

  // Clearing up merging state of repository after commit is done
  repo.stateCleanup();

  stdout.writeln(
    '\nUpdating ${parent.oid.sha.substring(0, 7)}..'
    '${masterOid.sha.substring(0, 7)}',
  );
}

/// Delete a branch.
///
/// Similar to `git branch -d new-branch`
void deleteBranch(Repository repo) {
  const name = 'new-branch';
  final tip = Branch.lookup(repo: repo, name: name).target.sha;
  Branch.delete(repo: repo, name: name);
  stdout.writeln('\nDeleted branch $name (was ${tip.substring(0, 7)}).');
}

/// Add a remote repository.
///
/// Similar to `git remote add origin https://some.url`
void addRemote(Repository repo) {
  const remoteName = 'origin';
  const url = 'https://some.url';
  Remote.create(repo: repo, name: remoteName, url: url);
}

/// View remote URLs.
///
/// Similar to `git remote -v`
void viewRemoteUrls(Repository repo) {
  for (final remoteName in repo.remotes) {
    final remote = Remote.lookup(repo: repo, name: remoteName);
    stdout.writeln('\n${remote.name}  ${remote.url} (fetch)');
    stdout.writeln('${remote.name}  ${remote.url} (push)');
  }
}

/// Remove a remote repository.
///
/// Similar to `git remote remove origin`
void removeRemote(Repository repo) {
  Remote.delete(repo: repo, name: 'origin');
}

/// Pull changes from a remote repository.
///
/// Similar to `git pull`
void pullChanges(Repository repo) {
  // Prepare "origin" repository to pull from
  final originDir = setupRepo(
    Directory(path.join('test', 'assets', 'test_repo')),
  );

  // Add remote
  const remoteName = 'origin';
  final remote = Remote.create(
    repo: repo,
    name: remoteName,
    url: originDir.path,
  );

  // Fetch changes
  remote.fetch();

  // Merge changes
  final theirHead = Reference.lookup(
    repo: repo,
    name: 'refs/remotes/origin/master',
  ).target;
  final analysis = Merge.analysis(repo: repo, theirHead: theirHead);

  // In reality there should be more checks for analysis result (if we should
  // perform merge, or checkout if fast-forward is available, etc.)
  if (analysis.result.contains(GitMergeAnalysis.normal)) {
    final commit = AnnotatedCommit.lookup(repo: repo, oid: theirHead);
    Merge.commit(repo: repo, commit: commit);
  }

  // Make merge commit
  repo.index.write();
  Commit.create(
    repo: repo,
    updateRef: 'HEAD',
    author: repo.defaultSignature,
    committer: repo.defaultSignature,
    message: 'Merge branch "master" of some remote\n',
    tree: Tree.lookup(repo: repo, oid: repo.index.writeTree()),
    parents: [
      Commit.lookup(repo: repo, oid: repo.head.target),
      Commit.lookup(repo: repo, oid: theirHead),
    ],
  );
  repo.stateCleanup();

  // Remove "origin" repository
  originDir.deleteSync(recursive: true);
}

/// Push changes to a remote repository.
///
/// Similar to `git push bare master`
void pushChanges(Repository repo) {
  // Prepare bare repository to push to
  final bareDir = setupRepo(
    Directory(path.join('test', 'assets', 'empty_bare.git')),
  );

  // Add remote
  const remoteName = 'bare';
  final url = bareDir.path;
  Remote.create(repo: repo, name: remoteName, url: url);
  Remote.addPush(repo: repo, remote: remoteName, refspec: 'refs/heads/master');

  // Push changes
  final remote = Remote.lookup(repo: repo, name: remoteName);
  remote.push();

  // Remove bare repository
  bareDir.deleteSync(recursive: true);
}

/// Push a new branch to remote repository.
///
/// Similar to `git push -u another-origin new-branch`
void pushNewBranch(Repository repo) {
  // Prepare bare repository to push to
  final bareDir = setupRepo(
    Directory(path.join('test', 'assets', 'empty_bare.git')),
  );

  // Create new branch
  final branch = Branch.create(
    repo: repo,
    name: 'new-branch',
    target: Commit.lookup(repo: repo, oid: repo.head.target),
  );

  // Add remote
  const remoteName = 'another-origin';
  final url = bareDir.path;
  Remote.create(repo: repo, name: remoteName, url: url);
  Remote.addPush(
    repo: repo,
    remote: remoteName,
    refspec: 'refs/heads/new-branch',
  );

  // Set upstream for the branch
  final trackingRef = Reference.create(
    repo: repo,
    name: 'refs/remotes/bare/new-branch',
    target: 'refs/heads/new-branch',
  );
  branch.setUpstream(trackingRef.shorthand);

  // Push new branch
  final remote = Remote.lookup(repo: repo, name: remoteName);
  remote.push();

  // Remove bare repository
  bareDir.deleteSync(recursive: true);
}
6
likes
150
pub points
33%
popularity

Publisher

verified publisherdartgit.dev

Dart bindings to libgit2, provides ability to use libgit2 library in Dart and Flutter.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

equatable, ffi, flutter, git2dart_binaries, meta, path

More

Packages that depend on git2dart