git_operations 0.0.1 copy "git_operations: ^0.0.1" to clipboard
git_operations: ^0.0.1 copied to clipboard

Git operation workflows built on path_utils command execution.

git_operations #

Git operation workflows for Dart: checkout, stash, and pull with user-guided handling of dirty working trees and untracked files. Built on path_utils for command execution and path resolution.

Overview #

git_operations orchestrates common git workflows across one or many repositories. It runs git through a pluggable command runner, logs progress through your existing callbacks, and delegates conflict decisions to your UI (dialogs, CLI prompts, or test fakes).

The package does not render UI itself. You provide resolver callbacks that return what the user chose to do.

Features #

GitRepository #

Minimal contract your repository model implements:

Member Purpose
name Label used in logs and user messages
currentBranchOnDisk Branch name for conflict prompts (may be null)
getExpandedPath(log) Resolve tildes, env vars, and symlinks to the directory git should run in
class MyRepo implements GitRepository {
  @override
  String get name => 'my-app';

  @override
  String? get currentBranchOnDisk => 'main';

  @override
  String getExpandedPath(void Function(String message) log) => expandPath('~/projects/my-app');
}

GitOperationService #

Main entry point. Construct it with GitOperationDependencies.

Method What it does
runCheckoutHandlingUntrackedFiles git checkout <branch> with dirty-tree and untracked-file handling
stashChanges git stash push -u (optional file list for partial untracked stashes)
popStash git stash pop with user-visible success/error messages
pullLatestChangesForRepos git pull across a list of repositories

After a successful checkout or pull, the service calls BaseRepositoryStateService.refreshRepoState so your app can refresh branch/status display.

Conflict resolution #

Before checkout or pull, the service checks for uncommitted changes (git status --porcelain -uno). If the working tree is dirty or checkout fails due to untracked files, it calls your resolver and acts on the response.

Untracked files (ResolveUntrackedFilesFunction) — triggered when checkout would overwrite untracked paths:

UserUntrackedFileAction Behavior
stashAndRetry Stash listed files, retry checkout
deleteAndRetry git clean -fd on listed files, retry checkout
skipRepository Skip this repo; result has skippedViaUserInput: true
cancelBatch Stop the outer batch; result has batchAborted: true
null (dismissed) Leave checkout failed

Dirty working tree (ResolveDirtyWorkdirFunction) — triggered when tracked files have local changes:

UserDirtyWorkdirAction Behavior
stashAndRetry Stash all changes (including untracked via -u), continue the operation
skipChange Skip this repo only
cancel Abort the batch (batchAborted: true)
null (dismissed) Treated as cancel
Future<UserUntrackedFileAction?> showUntrackedDialog(
  String repoName,
  String branchName,
  List<String> untrackedFiles,
) async {
  // Show UI, return user choice
}

Future<UserDirtyWorkdirAction?> showDirtyDialog(
  String repoName,
  String branchName,
  String porcelainStatus,
) async {
  // Show UI, return user choice
}

Batch multi-repo pull #

pullLatestChangesForRepos iterates repositories in order. For each repo it expands the path, handles a dirty working tree if needed, then runs git pull.

  • Per-repo skip — user chooses skipChange or skipRepository; pull continues for remaining repos.
  • Batch cancel — user chooses cancel or cancelBatch; loop stops and the result has batchAborted: true.
  • Auto-stash — when the user stashes during pull, affected repo names are collected in stashedRepoNames on the final result.
final result = await gitOps.pullLatestChangesForRepos(selectedRepos);

if (result.batchAborted) {
  // User cancelled — stop your wider orchestration
  return;
}

if (!result.success) {
  // At least one pull failed; see result.stderr
}

for (final name in result.stashedRepoNames) {
  // Offer to pop stash for repos stashed during this pull
}

The same batchAborted flag is set on individual checkout results when the user cancels from an untracked-file or dirty-tree prompt during a multi-repo checkout loop.

GitCommandResult #

Result type for git commands and workflow steps:

Field Meaning
success Whether the git operation completed successfully
skippedViaUserInput User chose to skip this repo
batchAborted User cancelled the wider batch — stop processing remaining repos
stashed A stash was created as part of resolving a conflict
stashedRepoNames Repo names stashed during a multi-repo pull
hasUntrackedFilesError Checkout failed because untracked files would be overwritten
untrackedFiles Paths parsed from that checkout error

GitOperationDependencies #

Bundles everything GitOperationService needs beyond your repository models:

Field Purpose
commandRunner Runs git and supplies log/message callbacks
repositoryStateService Refresh app state after successful operations
resolveUntrackedFiles Untracked-file conflict callback
resolveDirtyWorkdir Dirty working-tree conflict callback
stashMessage git stash push -m message (default: 'Auto-stash')

Use GitOperationDependencies.fromExecutor when you already have a CommandExecutor from path_utils:

final executor = CommandExecutor(
  addStepExecutionLogService: logStep,
  addDebugLog: logDebug,
  showMessage: showMessage,
  executableFinder: finder,
);

final gitOps = GitOperationService(
  GitOperationDependencies.fromExecutor(
    executor: executor,
    repositoryStateService: myRepoStateService,
    resolveUntrackedFiles: showUntrackedDialog,
    resolveDirtyWorkdir: showDirtyDialog,
    stashMessage: 'Auto-stash by MyApp',
  ),
);

Logging and user-visible messages are read from the command runner — wire those callbacks once on CommandExecutor, not separately on the git service.

GitCommandRunner #

Abstraction over git execution. [CommandExecutorGitRunner] wraps path_utils's CommandExecutor for subprocess execution and git-specific error handling. Inject a custom implementation in tests to avoid running real git commands.

BaseRepositoryStateService #

Hook for keeping your UI or model in sync after git changes:

class MyRepoStateService implements BaseRepositoryStateService {
  @override
  Future<void> refreshRepoState(GitRepository repo, String expandedRepoPath) async {
    // Re-read branch, status, etc.
  }

  @override
  Future<String?> getWorkdirStatus(GitRepository repo, String expandedRepoPath) async {
    // Optional: return porcelain status for display
  }
}

User messages #

The service emits user-visible notifications (via showMessage on the command runner) for outcomes such as stash failures, batch cancellation, pull completion, and stash pop results. Step-by-step narrative goes to addStepExecutionLog; diagnostic detail goes to addDebugLog.

Dependency on path_utils #

git_operations depends on path_utils for:

  • CommandExecutor subprocess execution and environment setup
  • Path expansion helpers used by your GitRepository implementation

Git command execution, git-specific result parsing, and ignorable git errors (e.g. tag clobber warnings) live in this package.

Example #

The example/ app demonstrates:

  • Implementing GitRepository
  • Running git commands through GitCommandRunner
  • Browsing upstream git/git man pages from GitHub (Docs tab; requires network)

Man page text is upstream content from git/git, licensed under GPL-2.0. It is not bundled in this package.

cd example
flutter run -d linux   # or macos / windows

Testing #

dart pub get
dart test

Implement GitRepository with fixed paths in tests. Provide fake ResolveUntrackedFilesFunction / ResolveDirtyWorkdirFunction implementations and a test double for GitCommandRunner to exercise orchestration without a real git repo.

1
likes
160
points
85
downloads

Documentation

API reference

Publisher

verified publishergantt-sync.com

Weekly Downloads

Git operation workflows built on path_utils command execution.

Repository (GitHub)
View/report issues

License

Apache-2.0 (license)

Dependencies

path_utils

More

Packages that depend on git_operations