installGitattributes function
- required Directory directory,
- required GgLog ggLog,
- SortedProcessingList? sortedProcessingList,
- ProcessRunner? processRunner,
Ensures a .gitattributes file containing all lines
gitattributesRequiredLinesFor returns for the repository exists in every
repository of the ticket directory lies in, and that the merge=ours
driver is configured locally.
gg refuses to operate (e.g. gg do commit) when automatic EOL
conversion is not configured via .gitattributes. In addition, the
ticket workflow relies on merge=ours rules for state files and on
merge=union for the changelog so that merging main into a feature
branch does not produce textual conflicts. The referenced ours driver
only works once git config merge.ours.driver true has been set in each
repository; union is built into git.
Behaviour per repository:
- If
.gitattributesdoes not exist, it is created containing all required lines. - If
.gitattributesexists, every required line that is missing is appended individually. - If all required lines are already present, the file is left untouched.
- If a
.gitdirectory exists,git config merge.ours.driver trueis executed with the repository as the working directory so themerge=oursrules can be honored by git.
This used to be the do install-gitattributes command. It is not exposed
on the CLI any more — do add is the only caller and runs it for you.
Implementation
Future<void> installGitattributes({
required Directory directory,
required GgLog ggLog,
SortedProcessingList? sortedProcessingList,
ProcessRunner? processRunner,
}) async {
final list = sortedProcessingList ?? SortedProcessingList(ggLog: ggLog);
final runner = processRunner ?? ggRunProcess;
// Detect ticket folder -----------------------------------------------------
final String? ticketPath = WorkspaceUtils.detectTicketPath(
path.absolute(directory.path),
);
if (ticketPath == null) {
ggLog(cAction('Please run this command inside a ticket folder.'));
throw Exception(cDetail('Not inside a ticket folder'));
}
final ticketDir = Directory(ticketPath);
final ticketName = path.basename(ticketDir.path);
// Collect all repositories in the ticket ----------------------------------
final nodes = await list.get(directory: ticketDir, ggLog: ggLog);
if (nodes.isEmpty) {
ggLog(cWarn('⚠️ No repos in this ticket'));
return;
}
// Ensure .gitattributes and merge.ours driver in each repository ----------
for (final node in nodes) {
final repoDir = node.directory;
final repoName = path.basename(repoDir.path);
final requiredLines = gitattributesRequiredLinesFor(repoDir.path);
final attributesFile = File(path.join(repoDir.path, '.gitattributes'));
if (!attributesFile.existsSync()) {
await attributesFile.writeAsString('${requiredLines.join('\n')}\n');
ggLog('Created .gitattributes in $repoName.');
} else {
final existing = await attributesFile.readAsString();
final existingLines = const LineSplitter()
.convert(existing)
.map((l) => l.trim())
.toSet();
final missingLines = requiredLines
.where((l) => !existingLines.contains(l))
.toList();
if (missingLines.isNotEmpty) {
final needsLeadingNewline =
existing.isNotEmpty && !existing.endsWith('\n');
final prefix = needsLeadingNewline ? '\n' : '';
await attributesFile.writeAsString(
'$prefix${missingLines.join('\n')}\n',
mode: FileMode.append,
);
ggLog('Updated .gitattributes in $repoName.');
}
}
// Configure the `ours` merge driver locally so the `merge=ours`
// rules in .gitattributes resolve to a real git driver.
final gitDir = Directory(path.join(repoDir.path, '.git'));
if (!gitDir.existsSync()) {
ggLog(
cWarn(
'Skipping merge.ours driver config for $repoName because no '
'.git directory was found.',
),
);
continue;
}
final result = await runner(
'git',
<String>['config', 'merge.ours.driver', 'true'],
workingDirectory: repoDir.path,
runInShell: ggPlatform.isWindows,
);
if (result.exitCode != 0) {
ggLog(
cError(
'Failed to configure merge.ours driver in $repoName: '
'${result.stderr}',
),
);
throw Exception(
cError('git config merge.ours.driver true failed in $repoName'),
);
}
ggLog('Configured merge.ours driver in $repoName.');
}
ggLog(
'✓ Ensured .gitattributes for all repositories in ticket '
'$ticketName.',
);
}