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);
}
7
likes
150
points
159
downloads

Publisher

verified publisherdartgit.dev

Weekly Downloads

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

Repository (GitHub)

Documentation

API reference

License

MIT (license)

Dependencies

equatable, ffi, flutter, git2dart_binaries, meta, path

More

Packages that depend on git2dart