Skip to content

Commit

Permalink
Merge pull request #308 from awesomejerry/feature/bisect
Browse files Browse the repository at this point in the history
  • Loading branch information
kahole authored Nov 19, 2024
2 parents c7f6e53 + f70cde7 commit 212e592
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 11 deletions.
13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@
{
"command": "magit.toggle-all-folds-for-change-views",
"title": "Magit Toggle All Folds for Change Views"
},
{
"command": "magit.bisect",
"title": "Magit Bisect"
}
],
"menus": {
Expand Down Expand Up @@ -384,6 +388,10 @@
{
"command": "magit.toggle-all-folds-for-change-views",
"when": "editorLangId == magit"
},
{
"command": "magit.bisect",
"when": "editorLangId == magit"
}
]
},
Expand Down Expand Up @@ -735,6 +743,11 @@
"command": "magit.clear-and-abort-editor",
"key": "ctrl+c ctrl+k",
"when": "editorTextFocus && editorLangId == git-rebase"
},
{
"command": "magit.bisect",
"key": "shift+b",
"when": "editorTextFocus && editorLangId == 'magit' && vim.mode =~ /^(?!SearchInProgressMode|CommandlineInProgress).*$/"
}
]
},
Expand Down
141 changes: 141 additions & 0 deletions src/commands/bisectCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { commands } from 'vscode';
import { MenuState, MenuUtil } from '../menu/menu';
import { MagitBisectState } from '../models/magitBisectState';
import { MagitLogEntry } from '../models/magitLogCommit';
import { MagitRepository } from '../models/magitRepository';
import { Ref, RefType, Repository } from '../typings/git';
import { gitRun } from '../utils/gitRawRunner';
import MagitUtils from '../utils/magitUtils';
import ViewUtils from '../utils/viewUtils';
import LogView from '../views/logView';

const bisectingCommands = [
{ label: 's', description: 'Start', action: startBisecting },
{ label: 'g', description: 'Mark as good', action: markAsGood },
{ label: 'b', description: 'Mark as bad', action: markAsBad },
{ label: 'r', description: 'Reset', action: resetBisect },
];

export async function bisecting(repository: MagitRepository) {
const commands = [...bisectingCommands];
if (repository.bisectState?.bisecting) {
commands.shift();
} else {
commands.pop();
}
const menu = {
title: 'Bisecting',
commands
};

return MenuUtil.showMenu(menu, { repository });
}

async function startBisecting({ repository }: MenuState) {
await gitRun(repository.gitRepository, ['bisect', 'start']);
}

async function markAsGood({ repository }: MenuState) {
const ref = await MagitUtils.chooseRef(repository, 'Mark as good');
if (!ref) {
return;
}
await gitRun(repository.gitRepository, ['bisect', 'good', ref]);
}

async function markAsBad({ repository }: MenuState) {
const ref = await MagitUtils.chooseRef(repository, 'Mark as bad');
if (!ref) {
return;
}
await gitRun(repository.gitRepository, ['bisect', 'bad', ref]);
}

async function resetBisect({ repository }: MenuState) {
await gitRun(repository.gitRepository, ['bisect', 'reset']);
}

function matchWithFallback(log: string, primaryPattern: RegExp, fallbackPattern: RegExp) {
let match = log.match(primaryPattern);
if (!match) {
match = log.match(fallbackPattern);
}
return match ? match[1] : undefined;
}

export async function bisectingStatus(repository: Repository): Promise<MagitBisectState> {
try {
const output = await gitRun(repository, ['bisect', 'log']);
const bisectLog = output.stdout.split('\n').reverse().join('\n');

const goodPattern = /good: \[([a-f0-9]{40})\]/;
const goodFallbackPattern = /git bisect good ([a-f0-9]{40})/;

const badPattern = /bad: \[([a-f0-9]{40})\]/;
const badFallbackPattern = /git bisect bad ([a-f0-9]{40})/;

const goodCommit = matchWithFallback(bisectLog, goodPattern, goodFallbackPattern);
const badCommit = matchWithFallback(bisectLog, badPattern, badFallbackPattern);

return {
bisecting: true,
good: goodCommit,
bad: badCommit
};
} catch (err) {
console.log(err);
return {
bisecting: false,
};
}
}

export function addBisectRefsToLogEntry(magitState: MagitRepository) {
const { bisectState } = magitState;
if (!bisectState || !bisectState.bisecting) {
return (entry: MagitLogEntry) => entry;
}
return (entry: MagitLogEntry) => {
const refs = [];
if (entry.commit.hash === magitState.HEAD?.commit) {
refs.push('bisect-current');
}
if (entry.commit.hash === bisectState.good) {
refs.push('bisect-good');
}
if (entry.commit.hash === bisectState.bad) {
refs.push('bisect-bad');
}
return {
...entry,
refs: [...entry.refs, ...refs]
};
};
}

export function getBisectRefs(magitState: MagitRepository) {
const refs = [];
const { bisectState } = magitState;
if (bisectState && bisectState.bisecting) {
refs.push({
name: '(current)',
type: RefType.Tag,
commit: magitState.HEAD?.commit
});
if (bisectState.good) {
refs.push({
name: '(good)',
type: RefType.Tag,
commit: bisectState.good
});
}
if (bisectState.bad) {
refs.push({
name: '(bad)',
type: RefType.Tag,
commit: bisectState.bad
});
}
}
return refs;
}
4 changes: 4 additions & 0 deletions src/commands/statusCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Stash } from '../models/stash';
import { MagitRepository } from '../models/magitRepository';
import ViewUtils from '../utils/viewUtils';
import { scheduleForgeStatusAsync, forgeStatusCached } from '../forge';
import { bisectingStatus } from './bisectCommands';

const maxCommitsAheadBehind = 50;

Expand Down Expand Up @@ -176,6 +177,8 @@ export async function internalMagitStatus(repository: Repository): Promise<Magit

const forgeState = forgeStatusCached(remotes);

const bisectingStatusTask = bisectingStatus(repository);

return {
uri: repository.rootUri,
HEAD,
Expand All @@ -196,6 +199,7 @@ export async function internalMagitStatus(repository: Repository): Promise<Magit
submodules: repository.state.submodules,
gitRepository: repository,
forgeState: forgeState,
bisectState: await bisectingStatusTask
};
}

Expand Down
5 changes: 4 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { copySectionValueCommand } from './commands/copySectionValueCommands';
import { copyBufferRevisionCommands } from './commands/copyBufferRevisionCommands';
import { submodules } from './commands/submodulesCommands';
import { forgeRefreshInterval } from './forge';
import { bisecting } from './commands/bisectCommands';

export const magitRepositories: Map<string, MagitRepository> = new Map<string, MagitRepository>();
export const views: Map<string, DocumentView> = new Map<string, DocumentView>();
Expand Down Expand Up @@ -178,7 +179,9 @@ export function activate(context: ExtensionContext) {
commands.registerTextEditorCommand('magit.unstage-file', CommandPrimer.primeFileCommand(unstageFile)),

commands.registerTextEditorCommand('magit.copy-section-value', CommandPrimer.primeRepoAndView(copySectionValueCommand)),
commands.registerTextEditorCommand('magit.copy-buffer-revision', CommandPrimer.primeRepoAndView(copyBufferRevisionCommands))
commands.registerTextEditorCommand('magit.copy-buffer-revision', CommandPrimer.primeRepoAndView(copyBufferRevisionCommands)),

commands.registerTextEditorCommand('magit.bisect', CommandPrimer.primeRepoAndView(bisecting))
);

context.subscriptions.push(commands.registerTextEditorCommand('magit.toggle-fold', CommandPrimer.primeRepoAndView(async (repo: MagitRepository, view: DocumentView) => {
Expand Down
7 changes: 7 additions & 0 deletions src/models/magitBisectState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Commit } from "../typings/git";

export interface MagitBisectState {
bisecting: boolean;
good?: Commit['hash'];
bad?: Commit['hash'];
}
3 changes: 3 additions & 0 deletions src/models/magitRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Stash } from './stash';
import { PullRequest } from '../forge/model/pullRequest';
import { Uri } from 'vscode';
import { ForgeState } from '../forge/model/forgeState';
import { MagitBisectState } from './magitBisectState';

export interface MagitRepository {
readonly uri: Uri;
Expand All @@ -33,4 +34,6 @@ export interface MagitRepository {
readonly gitRepository: Repository;

readonly forgeState?: ForgeState;

readonly bisectState?: MagitBisectState;
}
20 changes: 20 additions & 0 deletions src/views/bisect/bisectingSectionView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { MagitBisectState } from '../../models/magitBisectState';
import GitTextUtils from '../../utils/gitTextUtils';
import { LineBreakView } from '../general/lineBreakView';
import { SemanticTextView } from '../general/semanticTextView';
import { TextView } from '../general/textView';
import { View } from '../general/view';

export class BisectingSectionView extends View {
get id() { return 'Bisecting_section'; }

constructor(bisectState: MagitBisectState) {
super();
if (bisectState.bisecting) {
this.addSubview(new TextView('Bisecting...'));
this.addSubview(new SemanticTextView(`Good: ${GitTextUtils.shortHash(bisectState.good)}`));
this.addSubview(new SemanticTextView(`Bad: ${GitTextUtils.shortHash(bisectState.bad)}`));
this.addSubview(new LineBreakView());
}
}
}
15 changes: 8 additions & 7 deletions src/views/helpView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,14 @@ export class HelpView extends DocumentView {

private static createHelpText(c: any) {
return `Popup and dwim commands
${HelpView.joinTexts(19, [`${c['magit.cherry-picking']} Cherry-pick`, `${c['magit.branching']} Branch`, `${c['magit.commit']} Commit`])}
${HelpView.joinTexts(19, [`${c['magit.diffing']} Diff`, `${c['magit.fetching']} Fetch`, `${c['magit.pulling']} Pull`])}
${HelpView.joinTexts(19, [`${c['magit.ignoring']} Ignore`, `${c['magit.logging']} Log`, `${c['magit.merging']} Merge`])}
${HelpView.joinTexts(19, [`${c['magit.remoting']} Remote`, `${c['magit.pushing']} Push`, `${c['magit.rebasing']} Rebase`])}
${HelpView.joinTexts(19, [`${c['magit.tagging']} Tag`, `${c['magit.reverting']} Revert`, `${c['magit.resetting']} Reset`])}
${HelpView.joinTexts(19, [`${c['magit.show-refs']} Show Refs`, `${c['magit.stashing']} Stash`, `${c['magit.running']} Run`])}
${HelpView.joinTexts(19, [`${c['magit.worktree']} Worktree`, `${c['magit.submodules']} Submodules`, `${c['magit.process-log']} Process Log`])}
${HelpView.joinTexts(19, [`${c['magit.cherry-picking']} Cherry-pick`, `${c['magit.branching']} Branch`, `${c['magit.bisect']} Bisect`])}
${HelpView.joinTexts(19, [`${c['magit.commit']} Commit`, `${c['magit.diffing']} Diff`, `${c['magit.fetching']} Fetch`])}
${HelpView.joinTexts(19, [`${c['magit.pulling']} Pull`, `${c['magit.ignoring']} Ignore`, `${c['magit.logging']} Log`])}
${HelpView.joinTexts(19, [`${c['magit.merging']} Merge`, `${c['magit.remoting']} Remote`, `${c['magit.pushing']} Push`])}
${HelpView.joinTexts(19, [`${c['magit.rebasing']} Rebase`, `${c['magit.tagging']} Tag`, `${c['magit.reverting']} Revert`])}
${HelpView.joinTexts(19, [`${c['magit.resetting']} Reset`, `${c['magit.show-refs']} Show Refs`, `${c['magit.stashing']} Stash`])}
${HelpView.joinTexts(19, [`${c['magit.running']} Run`, `${c['magit.worktree']} Worktree`, `${c['magit.submodules']} Submodules`])}
${HelpView.joinTexts(19, [`${c['magit.process-log']} Process Log`])}
Applying changes
${HelpView.joinTexts(17, [`${c['magit.apply-at-point']} Apply`, `${c['magit.stage']} Stage`, `${c['magit.unstage']} Unstage`])}
Expand Down
10 changes: 8 additions & 2 deletions src/views/logView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DocumentView } from './general/documentView';
import { TextView } from './general/textView';
import { Ref } from '../typings/git';
import ViewUtils from '../utils/viewUtils';
import { addBisectRefsToLogEntry, getBisectRefs } from '../commands/bisectCommands';

export default class LogView extends DocumentView {

Expand All @@ -18,11 +19,16 @@ export default class LogView extends DocumentView {

constructor(uri: Uri, log: MagitLog, magitState: MagitRepository, defaultBranches?: { [remoteName: string]: string }) {
super(uri);
const refs = magitState.remotes.reduce((prev, remote) => remote.branches.concat(prev), magitState.branches.concat(magitState.tags));
const refs = magitState.remotes.reduce(
(prev, remote) => remote.branches.concat(prev),
magitState.branches.concat(magitState.tags).concat(getBisectRefs(magitState))
);

this.subViews = [
new TextView(`Commits in ${log.revName}`),
...log.entries.map(entry => new CommitLongFormItemView(entry, refs, magitState.HEAD?.name, defaultBranches)),
...log.entries
.map(addBisectRefsToLogEntry(magitState))
.map(entry => new CommitLongFormItemView(entry, refs, magitState.HEAD?.name, defaultBranches)),
];
}

Expand Down
11 changes: 10 additions & 1 deletion src/views/magitStatusView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { getLatestGitError } from '../commands/commandPrimer';
import { PullRequestSectionView } from './forge/pullRequestSectionView';
import { IssueSectionView } from './forge/issueSectionView';
import { ErrorMessageView } from './errorMessageView';
import { BisectingSectionView } from './bisect/bisectingSectionView';
import { getBisectRefs } from '../commands/bisectCommands';

export default class MagitStatusView extends DocumentView {

Expand Down Expand Up @@ -59,6 +61,10 @@ export default class MagitStatusView extends DocumentView {
this.addSubview(new RevertingSectionView(magitState.revertingState, magitState.log));
}

if (magitState.bisectState?.bisecting) {
this.addSubview(new BisectingSectionView(magitState.bisectState));
}

if (magitState.untrackedFiles.length && !magitConfig.hiddenStatusSections.has('untracked')) {
this.addSubview(new ChangeSectionView(Section.Untracked, magitState.untrackedFiles));
}
Expand All @@ -75,7 +81,10 @@ export default class MagitStatusView extends DocumentView {
this.addSubview(new StashSectionView(magitState.stashes));
}

const refs = magitState.remotes.reduce((prev, remote) => remote.branches.concat(prev), magitState.branches.concat(magitState.tags));
const refs = magitState.remotes.reduce(
(prev, remote) => remote.branches.concat(prev),
magitState.branches.concat(magitState.tags).concat(getBisectRefs(magitState))
);

if (magitState.HEAD?.upstreamRemote?.ahead?.commits.length && !magitConfig.hiddenStatusSections.has('unmerged')) {
this.addSubview(new UnsourcedCommitSectionView(Section.UnmergedInto, magitState.HEAD.upstreamRemote, magitState.HEAD.upstreamRemote.ahead, refs));
Expand Down

0 comments on commit 212e592

Please sign in to comment.