mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Merge pull request #1228 from afarber/add-git-co-author
feat: expose gitCoAuthor setting in settings.json and document it
This commit is contained in:
@@ -21,7 +21,7 @@ You can update your `.qwenignore` file at any time. To apply the changes, you mu
|
|||||||
## How to use `.qwenignore`
|
## How to use `.qwenignore`
|
||||||
|
|
||||||
| Step | Description |
|
| Step | Description |
|
||||||
| ---------------------- | ------------------------------------------------------------ |
|
| ---------------------- | -------------------------------------------------------------------------------------- |
|
||||||
| **Enable .qwenignore** | Create a file named `.qwenignore` in your project root directory |
|
| **Enable .qwenignore** | Create a file named `.qwenignore` in your project root directory |
|
||||||
| **Add ignore rules** | Open `.qwenignore` file and add paths to ignore, example: `/archive/` or `apikeys.txt` |
|
| **Add ignore rules** | Open `.qwenignore` file and add paths to ignore, example: `/archive/` or `apikeys.txt` |
|
||||||
|
|
||||||
|
|||||||
@@ -51,11 +51,12 @@ Settings are organized into categories. All settings should be placed within the
|
|||||||
#### general
|
#### general
|
||||||
|
|
||||||
| Setting | Type | Description | Default |
|
| Setting | Type | Description | Default |
|
||||||
| ------------------------------- | ------- | ------------------------------------------ | ----------- |
|
| ------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------- | ----------- |
|
||||||
| `general.preferredEditor` | string | The preferred editor to open files in. | `undefined` |
|
| `general.preferredEditor` | string | The preferred editor to open files in. | `undefined` |
|
||||||
| `general.vimMode` | boolean | Enable Vim keybindings. | `false` |
|
| `general.vimMode` | boolean | Enable Vim keybindings. | `false` |
|
||||||
| `general.disableAutoUpdate` | boolean | Disable automatic updates. | `false` |
|
| `general.disableAutoUpdate` | boolean | Disable automatic updates. | `false` |
|
||||||
| `general.disableUpdateNag` | boolean | Disable update notification prompts. | `false` |
|
| `general.disableUpdateNag` | boolean | Disable update notification prompts. | `false` |
|
||||||
|
| `general.gitCoAuthor` | boolean | Automatically add a Co-authored-by trailer to git commit messages when commits are made through Qwen Code. | `true` |
|
||||||
| `general.checkpointing.enabled` | boolean | Enable session checkpointing for recovery. | `false` |
|
| `general.checkpointing.enabled` | boolean | Enable session checkpointing for recovery. | `false` |
|
||||||
|
|
||||||
#### output
|
#### output
|
||||||
|
|||||||
@@ -140,8 +140,6 @@ The theme file must be a valid JSON file that follows the same structure as a cu
|
|||||||
|
|
||||||
### Example Custom Theme
|
### Example Custom Theme
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<img src="https://gw.alicdn.com/imgextra/i1/O1CN01Em30Hc1jYXAdIgls3_!!6000000004560-2-tps-1009-629.png" alt=" " style="zoom:100%;text-align:center;margin: 0 auto;" />
|
<img src="https://gw.alicdn.com/imgextra/i1/O1CN01Em30Hc1jYXAdIgls3_!!6000000004560-2-tps-1009-629.png" alt=" " style="zoom:100%;text-align:center;margin: 0 auto;" />
|
||||||
|
|
||||||
### Using Your Custom Theme
|
### Using Your Custom Theme
|
||||||
@@ -150,12 +148,10 @@ The theme file must be a valid JSON file that follows the same structure as a cu
|
|||||||
- Or, set it as the default by adding `"theme": "MyCustomTheme"` to the `ui` object in your `settings.json`.
|
- Or, set it as the default by adding `"theme": "MyCustomTheme"` to the `ui` object in your `settings.json`.
|
||||||
- Custom themes can be set at the user, project, or system level, and follow the same [configuration precedence](./configuration.md) as other settings.
|
- Custom themes can be set at the user, project, or system level, and follow the same [configuration precedence](./configuration.md) as other settings.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Themes Preview
|
## Themes Preview
|
||||||
|
|
||||||
| Dark Theme | Preview | Light Theme | Preview |
|
| Dark Theme | Preview | Light Theme | Preview |
|
||||||
| :-: | :-: | :-: | :-: |
|
| :----------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||||
| ANSI | <img src="https://gw.alicdn.com/imgextra/i2/O1CN01ZInJiq1GdSZc9gHsI_!!6000000000645-2-tps-1140-934.png" style="zoom:30%;text-align:center;margin: 0 auto;" /> | ANSI Light | <img src="https://gw.alicdn.com/imgextra/i2/O1CN01IiJQFC1h9E3MXQj6W_!!6000000004234-2-tps-1140-934.png" style="zoom:30%;text-align:center;margin: 0 auto;" /> |
|
| ANSI | <img src="https://gw.alicdn.com/imgextra/i2/O1CN01ZInJiq1GdSZc9gHsI_!!6000000000645-2-tps-1140-934.png" style="zoom:30%;text-align:center;margin: 0 auto;" /> | ANSI Light | <img src="https://gw.alicdn.com/imgextra/i2/O1CN01IiJQFC1h9E3MXQj6W_!!6000000004234-2-tps-1140-934.png" style="zoom:30%;text-align:center;margin: 0 auto;" /> |
|
||||||
| Atom OneDark | <img src="https://gw.alicdn.com/imgextra/i2/O1CN01Zlx1SO1Sw21SkTKV3_!!6000000002310-2-tps-1140-934.png" style="zoom:30%;text-align:center;margin: 0 auto;" /> | Ayu Light | <img src="https://gw.alicdn.com/imgextra/i3/O1CN01zEUc1V1jeUJsnCgQb_!!6000000004573-2-tps-1140-934.png" alt=" " style="zoom:30%;text-align:center;margin: 0 auto;" /> |
|
| Atom OneDark | <img src="https://gw.alicdn.com/imgextra/i2/O1CN01Zlx1SO1Sw21SkTKV3_!!6000000002310-2-tps-1140-934.png" style="zoom:30%;text-align:center;margin: 0 auto;" /> | Ayu Light | <img src="https://gw.alicdn.com/imgextra/i3/O1CN01zEUc1V1jeUJsnCgQb_!!6000000004573-2-tps-1140-934.png" alt=" " style="zoom:30%;text-align:center;margin: 0 auto;" /> |
|
||||||
| Ayu | <img src="https://gw.alicdn.com/imgextra/i3/O1CN019upo6v1SmPhmRjzfN_!!6000000002289-2-tps-1140-934.png" alt=" " style="zoom:30%;text-align:center;margin: 0 auto;" /> | Default Light | <img src="https://gw.alicdn.com/imgextra/i4/O1CN01RHjrEs1u7TXq3M6l3_!!6000000005990-2-tps-1140-934.png" alt=" " style="zoom:30%;text-align:center;margin: 0 auto;" /> |
|
| Ayu | <img src="https://gw.alicdn.com/imgextra/i3/O1CN019upo6v1SmPhmRjzfN_!!6000000002289-2-tps-1140-934.png" alt=" " style="zoom:30%;text-align:center;margin: 0 auto;" /> | Default Light | <img src="https://gw.alicdn.com/imgextra/i4/O1CN01RHjrEs1u7TXq3M6l3_!!6000000005990-2-tps-1140-934.png" alt=" " style="zoom:30%;text-align:center;margin: 0 auto;" /> |
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ This guide provides solutions to common issues and debugging tips, including top
|
|||||||
The Qwen Code uses specific exit codes to indicate the reason for termination. This is especially useful for scripting and automation.
|
The Qwen Code uses specific exit codes to indicate the reason for termination. This is especially useful for scripting and automation.
|
||||||
|
|
||||||
| Exit Code | Error Type | Description |
|
| Exit Code | Error Type | Description |
|
||||||
| --------- | -------------------------- | ------------------------------------------------------------ |
|
| --------- | -------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||||
| 41 | `FatalAuthenticationError` | An error occurred during the authentication process. |
|
| 41 | `FatalAuthenticationError` | An error occurred during the authentication process. |
|
||||||
| 42 | `FatalInputError` | Invalid or missing input was provided to the CLI. (non-interactive mode only) |
|
| 42 | `FatalInputError` | Invalid or missing input was provided to the CLI. (non-interactive mode only) |
|
||||||
| 44 | `FatalSandboxError` | An error occurred with the sandboxing environment (e.g. Docker, Podman, or Seatbelt). |
|
| 44 | `FatalSandboxError` | An error occurred with the sandboxing environment (e.g. Docker, Podman, or Seatbelt). |
|
||||||
|
|||||||
@@ -1002,6 +1002,7 @@ export async function loadCliConfig(
|
|||||||
enableToolOutputTruncation: settings.tools?.enableToolOutputTruncation,
|
enableToolOutputTruncation: settings.tools?.enableToolOutputTruncation,
|
||||||
eventEmitter: appEvents,
|
eventEmitter: appEvents,
|
||||||
useSmartEdit: argv.useSmartEdit ?? settings.useSmartEdit,
|
useSmartEdit: argv.useSmartEdit ?? settings.useSmartEdit,
|
||||||
|
gitCoAuthor: settings.general?.gitCoAuthor,
|
||||||
output: {
|
output: {
|
||||||
format: outputSettingsFormat,
|
format: outputSettingsFormat,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -147,6 +147,16 @@ const SETTINGS_SCHEMA = {
|
|||||||
description: 'Disable update notification prompts.',
|
description: 'Disable update notification prompts.',
|
||||||
showInDialog: false,
|
showInDialog: false,
|
||||||
},
|
},
|
||||||
|
gitCoAuthor: {
|
||||||
|
type: 'boolean',
|
||||||
|
label: 'Git Co-Author',
|
||||||
|
category: 'General',
|
||||||
|
requiresRestart: false,
|
||||||
|
default: true,
|
||||||
|
description:
|
||||||
|
'Automatically add a Co-authored-by trailer to git commit messages when commits are made through Qwen Code.',
|
||||||
|
showInDialog: false,
|
||||||
|
},
|
||||||
checkpointing: {
|
checkpointing: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
label: 'Checkpointing',
|
label: 'Checkpointing',
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ export interface ConfigParameters {
|
|||||||
contextFileName?: string | string[];
|
contextFileName?: string | string[];
|
||||||
accessibility?: AccessibilitySettings;
|
accessibility?: AccessibilitySettings;
|
||||||
telemetry?: TelemetrySettings;
|
telemetry?: TelemetrySettings;
|
||||||
gitCoAuthor?: GitCoAuthorSettings;
|
gitCoAuthor?: boolean;
|
||||||
usageStatisticsEnabled?: boolean;
|
usageStatisticsEnabled?: boolean;
|
||||||
fileFiltering?: {
|
fileFiltering?: {
|
||||||
respectGitIgnore?: boolean;
|
respectGitIgnore?: boolean;
|
||||||
@@ -534,9 +534,9 @@ export class Config {
|
|||||||
useCollector: params.telemetry?.useCollector,
|
useCollector: params.telemetry?.useCollector,
|
||||||
};
|
};
|
||||||
this.gitCoAuthor = {
|
this.gitCoAuthor = {
|
||||||
enabled: params.gitCoAuthor?.enabled ?? true,
|
enabled: params.gitCoAuthor ?? true,
|
||||||
name: params.gitCoAuthor?.name ?? 'Qwen-Coder',
|
name: 'Qwen-Coder',
|
||||||
email: params.gitCoAuthor?.email ?? 'qwen-coder@alibabacloud.com',
|
email: 'qwen-coder@alibabacloud.com',
|
||||||
};
|
};
|
||||||
this.usageStatisticsEnabled = params.usageStatisticsEnabled ?? true;
|
this.usageStatisticsEnabled = params.usageStatisticsEnabled ?? true;
|
||||||
|
|
||||||
|
|||||||
@@ -608,6 +608,36 @@ describe('ShellTool', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle git commit with combined short flags like -am', async () => {
|
||||||
|
const command = 'git commit -am "Add feature"';
|
||||||
|
const invocation = shellTool.build({ command, is_background: false });
|
||||||
|
const promise = invocation.execute(mockAbortSignal);
|
||||||
|
|
||||||
|
resolveExecutionPromise({
|
||||||
|
rawOutput: Buffer.from(''),
|
||||||
|
output: '',
|
||||||
|
exitCode: 0,
|
||||||
|
signal: null,
|
||||||
|
error: null,
|
||||||
|
aborted: false,
|
||||||
|
pid: 12345,
|
||||||
|
executionMethod: 'child_process',
|
||||||
|
});
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
expect(mockShellExecutionService).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining(
|
||||||
|
'Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>',
|
||||||
|
),
|
||||||
|
expect.any(String),
|
||||||
|
expect.any(Function),
|
||||||
|
mockAbortSignal,
|
||||||
|
false,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not modify non-git commands', async () => {
|
it('should not modify non-git commands', async () => {
|
||||||
const command = 'npm install';
|
const command = 'npm install';
|
||||||
const invocation = shellTool.build({ command, is_background: false });
|
const invocation = shellTool.build({ command, is_background: false });
|
||||||
@@ -768,6 +798,69 @@ describe('ShellTool', () => {
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add co-author when git commit is prefixed with cd command', async () => {
|
||||||
|
const command = 'cd /tmp/test && git commit -m "Test commit"';
|
||||||
|
const invocation = shellTool.build({ command, is_background: false });
|
||||||
|
const promise = invocation.execute(mockAbortSignal);
|
||||||
|
|
||||||
|
resolveExecutionPromise({
|
||||||
|
rawOutput: Buffer.from(''),
|
||||||
|
output: '',
|
||||||
|
exitCode: 0,
|
||||||
|
signal: null,
|
||||||
|
error: null,
|
||||||
|
aborted: false,
|
||||||
|
pid: 12345,
|
||||||
|
executionMethod: 'child_process',
|
||||||
|
});
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
expect(mockShellExecutionService).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining(
|
||||||
|
'Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>',
|
||||||
|
),
|
||||||
|
expect.any(String),
|
||||||
|
expect.any(Function),
|
||||||
|
mockAbortSignal,
|
||||||
|
false,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add co-author to git commit with multi-line message', async () => {
|
||||||
|
const command = `git commit -m "Fix bug
|
||||||
|
|
||||||
|
This is a detailed description
|
||||||
|
spanning multiple lines"`;
|
||||||
|
const invocation = shellTool.build({ command, is_background: false });
|
||||||
|
const promise = invocation.execute(mockAbortSignal);
|
||||||
|
|
||||||
|
resolveExecutionPromise({
|
||||||
|
rawOutput: Buffer.from(''),
|
||||||
|
output: '',
|
||||||
|
exitCode: 0,
|
||||||
|
signal: null,
|
||||||
|
error: null,
|
||||||
|
aborted: false,
|
||||||
|
pid: 12345,
|
||||||
|
executionMethod: 'child_process',
|
||||||
|
});
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
expect(mockShellExecutionService).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining(
|
||||||
|
'Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>',
|
||||||
|
),
|
||||||
|
expect.any(String),
|
||||||
|
expect.any(Function),
|
||||||
|
mockAbortSignal,
|
||||||
|
false,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -334,13 +334,14 @@ export class ShellToolInvocation extends BaseToolInvocation<
|
|||||||
private addCoAuthorToGitCommit(command: string): string {
|
private addCoAuthorToGitCommit(command: string): string {
|
||||||
// Check if co-author feature is enabled
|
// Check if co-author feature is enabled
|
||||||
const gitCoAuthorSettings = this.config.getGitCoAuthor();
|
const gitCoAuthorSettings = this.config.getGitCoAuthor();
|
||||||
|
|
||||||
if (!gitCoAuthorSettings.enabled) {
|
if (!gitCoAuthorSettings.enabled) {
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a git commit command
|
// Check if this is a git commit command (anywhere in the command, e.g., after "cd /path &&")
|
||||||
const gitCommitPattern = /^git\s+commit/;
|
const gitCommitPattern = /\bgit\s+commit\b/;
|
||||||
if (!gitCommitPattern.test(command.trim())) {
|
if (!gitCommitPattern.test(command)) {
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,15 +350,27 @@ export class ShellToolInvocation extends BaseToolInvocation<
|
|||||||
|
|
||||||
Co-authored-by: ${gitCoAuthorSettings.name} <${gitCoAuthorSettings.email}>`;
|
Co-authored-by: ${gitCoAuthorSettings.name} <${gitCoAuthorSettings.email}>`;
|
||||||
|
|
||||||
// Handle different git commit patterns
|
// Handle different git commit patterns:
|
||||||
// Match -m "message" or -m 'message'
|
// Match -m "message" or -m 'message', including combined flags like -am
|
||||||
const messagePattern = /(-m\s+)(['"])((?:\\.|[^\\])*?)(\2)/;
|
// Use separate patterns to avoid ReDoS (catastrophic backtracking)
|
||||||
const match = command.match(messagePattern);
|
//
|
||||||
|
// Pattern breakdown:
|
||||||
|
// -[a-zA-Z]*m matches -m, -am, -nm, etc. (combined short flags)
|
||||||
|
// \s+ matches whitespace after the flag
|
||||||
|
// [^"\\] matches any char except double-quote and backslash
|
||||||
|
// \\. matches escape sequences like \" or \\
|
||||||
|
// (?:...|...)* matches normal chars or escapes, repeated
|
||||||
|
const doubleQuotePattern = /(-[a-zA-Z]*m\s+)"((?:[^"\\]|\\.)*)"/;
|
||||||
|
const singleQuotePattern = /(-[a-zA-Z]*m\s+)'((?:[^'\\]|\\.)*)'/;
|
||||||
|
const doubleMatch = command.match(doubleQuotePattern);
|
||||||
|
const singleMatch = command.match(singleQuotePattern);
|
||||||
|
const match = doubleMatch ?? singleMatch;
|
||||||
|
const quote = doubleMatch ? '"' : "'";
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
const [fullMatch, prefix, quote, existingMessage, closingQuote] = match;
|
const [fullMatch, prefix, existingMessage] = match;
|
||||||
const newMessage = existingMessage + coAuthor;
|
const newMessage = existingMessage + coAuthor;
|
||||||
const replacement = prefix + quote + newMessage + closingQuote;
|
const replacement = prefix + quote + newMessage + quote;
|
||||||
|
|
||||||
return command.replace(fullMatch, replacement);
|
return command.replace(fullMatch, replacement);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user