feat(git-workflow): Simplify git integration with --from-branch option

- Remove automatic git workflow and branch-tag switching - we are not ready for it yet

- Add --from-branch option to add-tag command for manual tag creation from git branch

- Remove git workflow configuration from config.json and assets

- Disable automatic tag switching functions in git-utils.js

- Add createTagFromBranch function for branch-based tag creation

- Support both CLI and MCP interfaces for --from-branch functionality

- Fix ES module imports in git-utils.js and utils.js

- Maintain user control over tag contexts without forced automation

The simplified approach allows users to create tags from their current git branch when desired, without the complexity and rigidity of automatic branch-tag synchronization. Users maintain full control over their tag contexts while having convenient tools for git-based workflows when needed.
This commit is contained in:
Eyal Toledano
2025-06-13 18:56:09 -04:00
parent 32236a0bc5
commit be0bf18f41
11 changed files with 205 additions and 367 deletions

View File

@@ -3715,6 +3715,10 @@ Examples:
'--copy-from <tag>',
'Copy tasks from the specified tag to the new tag'
)
.option(
'--from-branch',
'Create tag name from current git branch (ignores tagName argument)'
)
.option('-d, --description <text>', 'Optional description for the tag')
.action(async (tagName, options) => {
try {
@@ -3745,19 +3749,70 @@ Examples:
outputType: 'cli'
};
// Regular tag creation
const createOptions = {
copyFromCurrent: options.copyFromCurrent || false,
copyFromTag: options.copyFrom,
description: options.description
};
// Handle --from-branch option
if (options.fromBranch) {
const { createTagFromBranch } = await import(
'./task-manager/tag-management.js'
);
const gitUtils = await import('./utils/git-utils.js');
await createTag(tasksPath, tagName, createOptions, context, 'text');
// Check if we're in a git repository
if (!(await gitUtils.isGitRepository(projectRoot))) {
console.error(
chalk.red(
'Error: Not in a git repository. Cannot use --from-branch option.'
)
);
process.exit(1);
}
// Get current git branch
const currentBranch = await gitUtils.getCurrentBranch(projectRoot);
if (!currentBranch) {
console.error(
chalk.red('Error: Could not determine current git branch.')
);
process.exit(1);
}
// Create tag from branch
const branchOptions = {
copyFromCurrent: options.copyFromCurrent || false,
copyFromTag: options.copyFrom,
description:
options.description ||
`Tag created from git branch "${currentBranch}"`
};
await createTagFromBranch(
tasksPath,
currentBranch,
branchOptions,
context,
'text'
);
} else {
// Regular tag creation
const createOptions = {
copyFromCurrent: options.copyFromCurrent || false,
copyFromTag: options.copyFrom,
description: options.description
};
await createTag(tasksPath, tagName, createOptions, context, 'text');
}
// Handle auto-switch if requested
if (options.autoSwitch) {
const { useTag } = await import('./task-manager/tag-management.js');
await useTag(tasksPath, tagName, {}, context, 'text');
const finalTagName = options.fromBranch
? (await import('./utils/git-utils.js')).sanitizeBranchNameForTag(
await (await import('./utils/git-utils.js')).getCurrentBranch(
projectRoot
)
)
: tagName;
await useTag(tasksPath, finalTagName, {}, context, 'text');
}
} catch (error) {
console.error(chalk.red(`Error creating tag: ${error.message}`));

View File

@@ -1235,7 +1235,7 @@ async function createTagFromBranch(
// Import git utilities
const { sanitizeBranchNameForTag, isValidBranchForTag } = await import(
'../../utils/git-utils.js'
'../utils/git-utils.js'
);
// Create a consistent logFn object regardless of context
@@ -1341,7 +1341,7 @@ async function autoSwitchTagForBranch(
isGitRepository,
sanitizeBranchNameForTag,
isValidBranchForTag
} = await import('../../utils/git-utils.js');
} = await import('../utils/git-utils.js');
// Create a consistent logFn object regardless of context
const logFn = mcpLog || {
@@ -1475,13 +1475,8 @@ async function checkAndAutoSwitchTag(projectRoot, tasksPath, context = {}) {
const rawConfig = fs.readFileSync(configPath, 'utf8');
const config = JSON.parse(rawConfig);
// Check if git workflow is enabled
const gitWorkflowEnabled = config.tags?.enabledGitworkflow || false;
const autoSwitchEnabled = config.tags?.autoSwitchTagWithBranch || false;
if (!gitWorkflowEnabled || !autoSwitchEnabled) {
return null;
}
// Git workflow has been removed - return null to disable auto-switching
return null;
// Perform auto-switch
return await autoSwitchTagForBranch(

View File

@@ -524,15 +524,6 @@ function migrateConfigJson(configPath) {
modified = true;
}
// Add tags section if missing
if (!config.tags) {
config.tags = {
enabledGitworkflow: false,
autoSwitchTagWithBranch: false
};
modified = true;
}
if (modified) {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
if (process.env.TASKMASTER_DEBUG === 'true') {

View File

@@ -285,134 +285,9 @@ async function checkAndAutoSwitchGitTag(projectRoot, tasksPath) {
throw new Error('projectRoot is required for checkAndAutoSwitchGitTag');
}
try {
// Only proceed if we have a valid project root
if (!fs.existsSync(projectRoot)) {
return;
}
// Read configuration to check if git workflow is enabled
const configPath = path.join(projectRoot, '.taskmaster', 'config.json');
if (!fs.existsSync(configPath)) {
return; // No config, git workflow disabled
}
const rawConfig = fs.readFileSync(configPath, 'utf8');
const config = JSON.parse(rawConfig);
// Check if git workflow features are enabled
const gitWorkflowEnabled = config.tags?.enabledGitworkflow || false;
const autoSwitchEnabled = config.tags?.autoSwitchTagWithBranch || false;
if (!gitWorkflowEnabled || !autoSwitchEnabled) {
return; // Git integration disabled
}
// Check if we're in a git repository
if (!(await isGitRepository(projectRoot))) {
return; // Not a git repo
}
// Get current git branch
const currentBranch = await getCurrentBranch(projectRoot);
if (!currentBranch || !isValidBranchForTag(currentBranch)) {
return; // No valid branch for tag creation
}
// Determine expected tag name from branch
const expectedTag = sanitizeBranchNameForTag(currentBranch);
// Get current tag from state.json
const statePath = path.join(projectRoot, '.taskmaster', 'state.json');
let currentTag = 'master'; // Default fallback
if (fs.existsSync(statePath)) {
try {
const rawState = fs.readFileSync(statePath, 'utf8');
const state = JSON.parse(rawState);
currentTag = state.currentTag || 'master';
} catch (error) {
// Use default if state reading fails
}
}
// If we're already on the correct tag, nothing to do
if (currentTag === expectedTag) {
return;
}
// Check if the expected tag exists by reading tasks.json
if (!fs.existsSync(tasksPath)) {
return; // No tasks file to work with
}
const rawTasksData = fs.readFileSync(tasksPath, 'utf8');
const tasksData = JSON.parse(rawTasksData);
const tagExists = tasksData[expectedTag];
if (tagExists) {
// Tag exists, switch to it
console.log(
`Auto-switching to tag "${expectedTag}" for branch "${currentBranch}"`
);
// Update current tag in state.json
let state = {};
if (fs.existsSync(statePath)) {
const rawState = fs.readFileSync(statePath, 'utf8');
state = JSON.parse(rawState);
}
state.currentTag = expectedTag;
state.lastSwitched = new Date().toISOString();
// Ensure branchTagMapping exists and update it
if (!state.branchTagMapping) {
state.branchTagMapping = {};
}
state.branchTagMapping[currentBranch] = expectedTag;
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
} else {
// Tag doesn't exist, create it automatically
console.log(
`Auto-creating tag "${expectedTag}" for branch "${currentBranch}"`
);
// Create the tag with a descriptive name
const newTagData = {
tasks: [],
metadata: {
created: new Date().toISOString(),
updated: new Date().toISOString(),
description: `Automatically created from git branch "${currentBranch}"`
}
};
// Add the new tag to the tasks data
tasksData[expectedTag] = newTagData;
fs.writeFileSync(tasksPath, JSON.stringify(tasksData, null, 2), 'utf8');
// Update branch-tag mapping
let state = {};
if (fs.existsSync(statePath)) {
const rawState = fs.readFileSync(statePath, 'utf8');
state = JSON.parse(rawState);
}
state.currentTag = expectedTag;
state.lastSwitched = new Date().toISOString();
if (!state.branchTagMapping) {
state.branchTagMapping = {};
}
state.branchTagMapping[currentBranch] = expectedTag;
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
}
} catch (error) {
// Silently fail - don't break normal operations
console.debug(`Git auto-switch failed: ${error.message}`);
}
// DISABLED: Automatic git workflow is too rigid and opinionated
// Users should explicitly use git-tag commands if they want integration
return;
}
/**
@@ -427,148 +302,9 @@ function checkAndAutoSwitchGitTagSync(projectRoot, tasksPath) {
return; // Can't proceed without project root
}
try {
// Only proceed if we have a valid project root
if (!fs.existsSync(projectRoot)) {
return;
}
// Read configuration to check if git workflow is enabled
const configPath = path.join(projectRoot, '.taskmaster', 'config.json');
if (!fs.existsSync(configPath)) {
return; // No config, git workflow disabled
}
const rawConfig = fs.readFileSync(configPath, 'utf8');
const config = JSON.parse(rawConfig);
// Check if git workflow features are enabled
const gitWorkflowEnabled = config.tags?.enabledGitworkflow || false;
const autoSwitchEnabled = config.tags?.autoSwitchTagWithBranch || false;
if (!gitWorkflowEnabled || !autoSwitchEnabled) {
return; // Git integration disabled
}
// Check if we're in a git repository (synchronously)
if (!isGitRepositorySync(projectRoot)) {
return; // Not a git repo
}
// Get current git branch (synchronously)
const currentBranch = getCurrentBranchSync(projectRoot);
if (!currentBranch || !isValidBranchForTag(currentBranch)) {
return; // No valid branch for tag creation
}
// Determine expected tag name from branch
const expectedTag = sanitizeBranchNameForTag(currentBranch);
// Get current tag from state.json
const statePath = path.join(projectRoot, '.taskmaster', 'state.json');
let currentTag = 'master'; // default
if (fs.existsSync(statePath)) {
try {
const rawState = fs.readFileSync(statePath, 'utf8');
const state = JSON.parse(rawState);
currentTag = state.currentTag || 'master';
} catch (error) {
// Use default if state.json is corrupted
}
}
// If we're already on the correct tag, nothing to do
if (currentTag === expectedTag) {
return;
}
// Check if the expected tag exists in tasks.json
let tasksData = {};
if (fs.existsSync(tasksPath)) {
try {
const rawTasks = fs.readFileSync(tasksPath, 'utf8');
tasksData = JSON.parse(rawTasks);
} catch (error) {
return; // Can't read tasks file
}
}
const tagExists = tasksData[expectedTag];
if (tagExists) {
// Tag exists, switch to it
console.log(
`Auto-switching to tag "${expectedTag}" for branch "${currentBranch}"`
);
// Update current tag in state.json
let state = {};
if (fs.existsSync(statePath)) {
try {
const rawState = fs.readFileSync(statePath, 'utf8');
state = JSON.parse(rawState);
} catch (error) {
state = {}; // Start fresh if corrupted
}
}
state.currentTag = expectedTag;
state.lastSwitched = new Date().toISOString();
// Ensure branchTagMapping exists and update it
if (!state.branchTagMapping) {
state.branchTagMapping = {};
}
state.branchTagMapping[currentBranch] = expectedTag;
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
} else {
// Tag doesn't exist, create it automatically
console.log(
`Auto-creating tag "${expectedTag}" for branch "${currentBranch}"`
);
// Create the tag with a descriptive name
const newTagData = {
tasks: [],
metadata: {
created: new Date().toISOString(),
updated: new Date().toISOString(),
description: `Automatically created from git branch "${currentBranch}"`
}
};
// Add the new tag to the tasks data
tasksData[expectedTag] = newTagData;
fs.writeFileSync(tasksPath, JSON.stringify(tasksData, null, 2), 'utf8');
// Update branch-tag mapping and current tag
let state = {};
if (fs.existsSync(statePath)) {
try {
const rawState = fs.readFileSync(statePath, 'utf8');
state = JSON.parse(rawState);
} catch (error) {
state = {}; // Start fresh if corrupted
}
}
state.currentTag = expectedTag;
state.lastSwitched = new Date().toISOString();
if (!state.branchTagMapping) {
state.branchTagMapping = {};
}
state.branchTagMapping[currentBranch] = expectedTag;
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
}
} catch (error) {
// Silently fail - don't break normal operations
if (process.env.TASKMASTER_DEBUG === 'true') {
console.debug(`Git auto-switch failed: ${error.message}`);
}
}
// DISABLED: Automatic git workflow is too rigid and opinionated
// Users should explicitly use git-tag commands if they want integration
return;
}
/**