1
1
mirror of https://github.com/kahole/edamagit.git synced 2024-09-11 07:15:31 +03:00

Compare commits

...

10 Commits

Author SHA1 Message Date
kahole
95ebda5fb4 changelog 2023-07-11 20:21:16 +02:00
kahole
b02b539226 fix naming of stash to magitchanges func 2023-07-11 20:15:39 +02:00
Kristian Andersen Hole
8a9031f21a
Merge pull request #262 from bjackman/optimise
Fix status view for very out-of-sync repos [V2]
2023-07-11 20:15:19 +02:00
Kristian Andersen Hole
1f8f3e4ae0
Merge pull request #265 from vbh/stash-fold-hunks
Support fold/unfold diff hunks in stash detail view
2023-07-11 20:03:14 +02:00
Kristian Andersen Hole
6355baf768
Merge pull request #259 from tzemanovic/log-first-parent-switch
add a switch for log --first-parent
2023-07-11 19:52:15 +02:00
Bindu
93bf37d150 Support fold/unfold diff hunks in stash detail view 2023-07-08 23:30:22 -07:00
Brendan Jackman
9d0f6059a9 Fix hash list parsing in getCommitRange
Looks like when I wrote this code I didn't realise that
`String.prototype.split` never returns an empty array for a non-empty
separator. That is, for an empty input it returns an array with a single
empty string in it.

This means when there is nothing in the specified range, an empty string
is passed to `getCommit`. The upshot is that now that the range
specification bug fixed by the parent commit is fixed,
`pushRemoteStatus` always encounters an error and returns an empty
result, so the `Push: ` view in the UI disappears.

So we just need to explicitly account for the empty-string case here.

Also remove the comment, which seems to have leaked in during
developent, it doesn't apply to the code here (there is a `.slice` call
nearby but that's just to enforce `maxReults`).
2023-07-01 17:16:59 +02:00
Brendan Jackman
ca38e31b76 Fix range operator used in getCommitRange
The three-dot notation is a symmetric operation returning the difference
between two commits, i.e. all that are reachable from one but not the
other.

This didn't turn out to matter for the usage directly in
`internalMagitStatus` - we only call `getCommitRange` at most once there
anyway because we first check the `.ahead`/`.behind` counters provided
by the VSCode Git API.

However in `pushRemoteStatus` we call it unconditionall for both
"directions", which means the same set of commits show up in the
returned `.head` and `.behind` fields. This leads to the bug described
in https://github.com/kahole/edamagit/pull/257#issuecomment-1559101338.

The fix is just to use the asymmetric range operator: `..`.
2023-07-01 17:15:33 +02:00
Brendan Jackman
014d615fab Revert "Revert "avoid N getCommit calls when N commits are out of sync with upstream""
This reverts commit f7cdaeeda4.

As noted in
https://github.com/kahole/edamagit/pull/257#issuecomment-1559101338,
that commit is broken when the current branch is out of sync with
its upstream, and a push remote is configured for it.

This will be fixed in the next commit.
2023-07-01 14:23:12 +02:00
Tomáš Zemanovič
42ebfd1b32
add a switch for log --first-parent 2023-05-26 11:36:28 +02:00
12 changed files with 131 additions and 44 deletions

View File

@ -1,5 +1,10 @@
# Changelog
## [0.6.44]
- Improved performance of status view and fix commit ahead/behind list for very out-of-sync repos (@bjackman Brendan Jackman)
- add a switch for log --first-parent (@tzemanovic Tomas Zemanovic)
- Fold/unfold diff hunks in stash detail view (@vbh Bindu)
## [0.6.43]
- When discarding a file change fails because of resolved conflicts (e.g. during merge) prompt to checkout our stage,
their stage or to leave conflicts for manual resolving. @tzemanovic (Tomas Zemanovic)

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "magit",
"version": "0.6.43",
"version": "0.6.44",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "magit",
"version": "0.6.43",
"version": "0.6.44",
"license": "MIT",
"dependencies": {
"@vscode/iconv-lite-umd": "^0.7.0",

View File

@ -7,7 +7,7 @@
"author": {
"name": "Kristian Andersen Hole"
},
"version": "0.6.43",
"version": "0.6.44",
"engines": {
"vscode": "^1.50.0"
},

View File

@ -3,6 +3,7 @@ import { MagitRepository } from '../models/magitRepository';
import { gitRun } from '../utils/gitRawRunner';
import { DiffView } from '../views/diffView';
import { MenuUtil, MenuState } from '../menu/menu';
import GitTextUtils from '../utils/gitTextUtils';
import { PickMenuUtil, PickMenuItem } from '../menu/pickMenu';
import { StashDetailView } from '../views/stashDetailView';
import MagitUtils from '../utils/magitUtils';
@ -14,6 +15,7 @@ import { Status } from '../typings/git';
import { MagitChange } from '../models/magitChange';
import { Stash } from '../models/stash';
import ViewUtils from '../utils/viewUtils';
import { constants } from 'buffer';
const diffingMenu = {
title: 'Diffing',
@ -102,9 +104,51 @@ async function showStash({ repository }: MenuState) {
}
}
function stashToMagitChanges(nameStatusText: string, diff: string): MagitChange[] {
const DIFF_PREFIX = 'diff --git';
const filesWithStatus = nameStatusText.split(Constants.LineSplitterRegex).filter(t => t !== '').map(s => s.split('\t'));
const diffs = diff.split(DIFF_PREFIX).filter(r => r !== '');
if (filesWithStatus.length !== diffs.length) {
return [];
}
return diffs.map((diff, idx) => {
const [status, ...paths] = filesWithStatus[idx];
const uri = Uri.file(paths[paths.length - 1]);
const fileStatus = getStatusFromString(status);
return {
diff,
uri,
originalUri: uri,
status: fileStatus,
renameUri: undefined,
relativePath: paths.join(' -> '),
hunks: diff ? GitTextUtils.diffToHunks(diff, uri) : undefined,
};
});
}
function getStatusFromString(status: String): number {
switch (status.charAt(0)) {
case 'A':
return Status.INDEX_ADDED;
case 'C':
return Status.INDEX_COPIED ;
case 'D':
return Status.DELETED;
case 'R' :
return Status.INDEX_RENAMED;
case 'M':
default:
return Status.MODIFIED;
}
}
export async function showStashDetail(repository: MagitRepository, stash: Stash) {
const uri = StashDetailView.encodeLocation(repository, stash);
const nameStatusTask = gitRun(repository.gitRepository, ['stash', 'show', '--name-status', `stash@{${stash.index}}`]);
const stashShowTask = gitRun(repository.gitRepository, ['stash', 'show', '-p', `stash@{${stash.index}}`]);
let stashUntrackedFiles: MagitChange[] = [];
try {
@ -124,9 +168,10 @@ export async function showStashDetail(repository: MagitRepository, stash: Stash)
} catch { }
const nameStatusText = (await nameStatusTask).stdout;
const stashDiff = (await stashShowTask).stdout;
return ViewUtils.showView(uri, new StashDetailView(uri, stash, stashDiff, stashUntrackedFiles));
return ViewUtils.showView(uri, new StashDetailView(uri, stash, stashToMagitChanges(nameStatusText, stashDiff), stashUntrackedFiles));
}
async function showCommit({ repository }: MenuState) {

View File

@ -24,7 +24,8 @@ const loggingMenu = {
const switches: Switch[] = [
{ key: '-D', name: '--simplify-by-decoration', description: 'Simplify by decoration' },
{ key: '-g', name: '--graph', description: 'Show graph', activated: true },
{ key: '-d', name: '--decorate', description: 'Show refnames', activated: true }
{ key: '-d', name: '--decorate', description: 'Show refnames', activated: true },
{ key: '-p', name: '--first-parent', description: 'First parent', activated: false },
];
const options: Option[] = [
@ -126,6 +127,9 @@ function createLogArgs(switches: Switch[], options: Option[]) {
if (switchMap['-g'].activated) {
args.push(switchMap['-g'].name);
}
if (switchMap['-p'].activated) {
args.push(switchMap['-p'].name);
}
return args;
}

View File

@ -6,7 +6,7 @@ import GitTextUtils from '../utils/gitTextUtils';
import MagitUtils from '../utils/magitUtils';
import MagitStatusView from '../views/magitStatusView';
import { Status, Commit, RefType, Repository, Change, Ref } from '../typings/git';
import { MagitBranch, MagitUpstreamRef } from '../models/magitBranch';
import { MagitBranch, MagitCommitList, MagitUpstreamRef } from '../models/magitBranch';
import { gitRun, LogLevel } from '../utils/gitRawRunner';
import * as Constants from '../common/constants';
import { getCommit } from '../utils/commitCache';
@ -19,6 +19,8 @@ import { MagitRepository } from '../models/magitRepository';
import ViewUtils from '../utils/viewUtils';
import { scheduleForgeStatusAsync, forgeStatusCached } from '../forge';
const maxCommitsAheadBehind = 50;
export async function magitRefresh() { }
export async function magitStatus(): Promise<any> {
@ -70,18 +72,18 @@ export async function internalMagitStatus(repository: Repository): Promise<Magit
const logTask = repository.state.HEAD?.commit ? repository.log({ maxEntries: 100 }) : Promise.resolve([]);
if (repository.state.HEAD?.commit) {
getCommit(repository, repository.state.HEAD?.commit);
}
let ahead, behind: Promise<MagitCommitList> | undefined;
let commitsAheadUpstream: string[] = [], commitsBehindUpstream: string[] = [];
if (repository.state.HEAD?.ahead || repository.state.HEAD?.behind) {
const ref = repository.state.HEAD.name;
const args = ['rev-list', '--left-right', `${ref}...${ref}@{u}`];
const res = (await gitRun(repository, args, {}, LogLevel.None)).stdout;
[commitsAheadUpstream, commitsBehindUpstream] = GitTextUtils.parseRevListLeftRight(res);
commitsAheadUpstream.map(c => getCommit(repository, c));
commitsBehindUpstream.map(c => getCommit(repository, c));
const ref = repository.state.HEAD?.name;
const upstream = `${ref}@{u}`;
// We actually must have a named ref if there is "ahead"/"behind"; you can't
// have an "upstream" without being on a named branch. So the && ref is just
// to satisfy the type checker.
if (repository.state.HEAD?.ahead && ref) {
ahead = getCommitRange(repository, upstream, ref, maxCommitsAheadBehind);
}
if (repository.state.HEAD?.behind && ref) {
behind = getCommitRange(repository, ref, upstream, maxCommitsAheadBehind);
}
const workingTreeChanges_NoUntracked = repository.state.workingTreeChanges
@ -150,8 +152,12 @@ export async function internalMagitStatus(repository: Repository): Promise<Magit
HEAD.upstreamRemote = HEAD.upstream;
HEAD.upstreamRemote.commit = await upstreamRemoteCommitDetails;
HEAD.upstreamRemote.commitsAhead = await Promise.all(commitsAheadUpstream.map(hash => getCommit(repository, hash)));
HEAD.upstreamRemote.commitsBehind = await Promise.all(commitsBehindUpstream.map(hash => getCommit(repository, hash)));
if (ahead) {
HEAD.upstreamRemote.ahead = await ahead;
}
if (behind) {
HEAD.upstreamRemote.behind = await behind;
}
HEAD.upstreamRemote.rebase = (await isRebaseUpstream) === 'true';
}
} catch { }
@ -201,6 +207,22 @@ function toMagitChange(repository: Repository, change: Change, diff?: string): M
return magitChange;
}
async function getCommitRange(repository: Repository, from: string, to: string, maxResults: number): Promise<MagitCommitList> {
const args = ['log', '--format=format:%H', `${from}..${to}`, '-n', `${Math.trunc(maxResults) + 1}`];
let result;
try {
result = await gitRun(repository, args, {}, LogLevel.Error);
} catch (error) {
return {commits: [], truncated: false};
}
const out = result.stdout.trim();
const hashes = out ? out.split(Constants.LineSplitterRegex) : [];
return {
commits: await Promise.all(hashes.slice(0, maxResults).map(hash => getCommit(repository, hash))),
truncated: hashes.length > maxResults,
};
}
async function pushRemoteStatus(repository: Repository): Promise<MagitUpstreamRef | undefined> {
try {
const HEAD = repository.state.HEAD;
@ -208,17 +230,20 @@ async function pushRemoteStatus(repository: Repository): Promise<MagitUpstreamRe
if (HEAD?.name && pushRemote) {
const args = ['rev-list', '--left-right', `${HEAD.name}...${pushRemote}/${HEAD.name}`];
const res = (await gitRun(repository, args, {}, LogLevel.None)).stdout;
const [commitsAheadPushRemote, commitsBehindPushRemote] = GitTextUtils.parseRevListLeftRight(res);
const commitsAhead = await Promise.all(commitsAheadPushRemote.map(c => getCommit(repository, c)));
const commitsBehind = await Promise.all(commitsBehindPushRemote.map(c => getCommit(repository, c)));
const ahead = getCommitRange(repository, `${pushRemote}/${HEAD.name}`, HEAD.name, maxCommitsAheadBehind);
const behind = getCommitRange(repository, HEAD.name, `${pushRemote}/${HEAD.name}`, maxCommitsAheadBehind);
const refs = await getRefs(repository);
const pushRemoteCommit = refs.find(ref => ref.remote === pushRemote && ref.name === `${pushRemote}/${HEAD.name}`)?.commit;
const pushRemoteCommitDetails = pushRemoteCommit ? getCommit(repository, pushRemoteCommit) : Promise.resolve(undefined);
return { remote: pushRemote, name: HEAD.name, commit: await pushRemoteCommitDetails, commitsAhead, commitsBehind };
return {
remote: pushRemote,
name: HEAD.name,
commit: await pushRemoteCommitDetails,
ahead: await ahead,
behind: await behind,
};
}
} catch { }
}

View File

@ -7,9 +7,13 @@ export interface MagitBranch extends Branch {
tag?: Ref;
}
export interface MagitCommitList {
commits: Commit[];
truncated: boolean;
}
export interface MagitUpstreamRef extends UpstreamRef {
commit?: Commit;
commitsAhead?: Commit[];
commitsBehind?: Commit[];
ahead?: MagitCommitList;
behind?: MagitCommitList;
rebase?: boolean;
}

View File

@ -3,17 +3,20 @@ import { Section, SectionHeaderView } from '../general/sectionHeader';
import { Commit, UpstreamRef, Ref } from '../../typings/git';
import { LineBreakView } from '../general/lineBreakView';
import { CommitItemView } from './commitSectionView';
import { MagitCommitList } from '../../models/magitBranch';
export class UnsourcedCommitSectionView extends View {
isFoldable = true;
static maxEntries = 256;
get id() { return this.section.toString(); }
constructor(private section: Section, upstream: UpstreamRef, commits: Commit[], refs: Ref[]) {
constructor(private section: Section, upstream: UpstreamRef, list: MagitCommitList, refs: Ref[]) {
super();
this.subViews = [
new SectionHeaderView(section, commits.length, `${upstream.remote}/${upstream.name}`),
...commits.map(commit => new CommitItemView(commit, undefined, refs)),
new SectionHeaderView(section, list.commits.length, `${upstream.remote}/${upstream.name}`, list.truncated),
...list.commits.map(commit => new CommitItemView(commit, undefined, refs)),
new LineBreakView()
];
}

View File

@ -18,11 +18,12 @@ export enum Section {
Tags = 'Tags',
PullRequests = 'Pull Requests',
Issues = 'Issues',
Changes='Changes',
}
export class SectionHeaderView extends UnclickableTextView {
constructor(section: Section, count?: number, extraText?: string) {
super(`${section.valueOf()}${extraText ? ' ' + extraText + '' : ''}${count ? ' (' + count + ')' : ''}`);
constructor(section: Section, count?: number, extraText?: string, truncated=false) {
super(`${section.valueOf()}${extraText ? ' ' + extraText : ''}${count ? ` (${count}${truncated ? '+': ''})` : ''}`);
}
}

View File

@ -77,19 +77,19 @@ export default class MagitStatusView extends DocumentView {
const refs = magitState.remotes.reduce((prev, remote) => remote.branches.concat(prev), magitState.branches.concat(magitState.tags));
if (magitState.HEAD?.upstreamRemote?.commitsAhead?.length && !magitConfig.hiddenStatusSections.has('unmerged')) {
this.addSubview(new UnsourcedCommitSectionView(Section.UnmergedInto, magitState.HEAD.upstreamRemote, magitState.HEAD.upstreamRemote.commitsAhead, refs));
} else if (magitState.HEAD?.pushRemote?.commitsAhead?.length && !magitConfig.hiddenStatusSections.has('unpushed')) {
this.addSubview(new UnsourcedCommitSectionView(Section.UnpushedTo, magitState.HEAD.pushRemote, magitState.HEAD.pushRemote.commitsAhead, refs));
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));
} else if (magitState.HEAD?.pushRemote?.ahead?.commits.length && !magitConfig.hiddenStatusSections.has('unpushed')) {
this.addSubview(new UnsourcedCommitSectionView(Section.UnpushedTo, magitState.HEAD.pushRemote, magitState.HEAD.pushRemote.ahead, refs));
}
if (magitState.log.length > 0 && !magitState.HEAD?.upstreamRemote?.commitsAhead?.length && !magitConfig.hiddenStatusSections.has('recent commits')) {
if (magitState.log.length > 0 && !magitState.HEAD?.upstreamRemote?.ahead?.commits.length && !magitConfig.hiddenStatusSections.has('recent commits')) {
this.addSubview(new CommitSectionView(Section.RecentCommits, magitState.log.slice(0, 10), refs));
}
if (magitState.HEAD?.upstreamRemote?.commitsBehind?.length && !magitConfig.hiddenStatusSections.has('unpulled')) {
this.addSubview(new UnsourcedCommitSectionView(Section.UnpulledFrom, magitState.HEAD.upstreamRemote, magitState.HEAD.upstreamRemote.commitsBehind, refs));
} else if (magitState.HEAD?.pushRemote?.commitsBehind?.length) {
this.addSubview(new UnsourcedCommitSectionView(Section.UnpulledFrom, magitState.HEAD.pushRemote, magitState.HEAD.pushRemote.commitsBehind, refs));
if (magitState.HEAD?.upstreamRemote?.behind?.commits.length && !magitConfig.hiddenStatusSections.has('unpulled')) {
this.addSubview(new UnsourcedCommitSectionView(Section.UnpulledFrom, magitState.HEAD.upstreamRemote, magitState.HEAD.upstreamRemote.behind, refs));
} else if (magitState.HEAD?.pushRemote?.behind?.commits.length) {
this.addSubview(new UnsourcedCommitSectionView(Section.UnpulledFrom, magitState.HEAD.pushRemote, magitState.HEAD.pushRemote.behind, refs));
}
if (magitState.forgeState?.pullRequests?.length && !magitConfig.hiddenStatusSections.has('pull requests')) {

View File

@ -13,7 +13,7 @@ export class StashDetailView extends DocumentView {
static UriPath: string = 'stash.magit';
needsUpdate = false;
constructor(public uri: Uri, stash: Stash, diff: string, untrackedFiles: MagitChange[]) {
constructor(public uri: Uri, stash: Stash, diffChanges: MagitChange[], untrackedFiles: MagitChange[] ) {
super(uri);
this.addSubview(new TextView(`Stash@{${stash.index}} ${stash.description}`));
@ -22,7 +22,7 @@ export class StashDetailView extends DocumentView {
this.addSubview(new ChangeSectionView(Section.Untracked, untrackedFiles, `-stashDetail@{${stash.index}}`));
}
this.addSubview(new TextView(diff));
this.addSubview(new ChangeSectionView(Section.Changes, diffChanges, `-stashDetail@{${stash.index}}`));
}
public update(state: MagitRepository): void { }

View File

@ -38,7 +38,7 @@
]
},
"sectionHeader": {
"match": "^(Untracked files|((Unstaged|Staged) changes)|Stashes|Recent commits|Unmerged into|Unpushed to|Unpulled from|HEAD|Branches|Remote|Tags|Pull Requests|Issues|GitError!|(Merging .*(?= \\())|(Rebasing .* onto .*$)|Cherry Picking|Reverting|Stash@{\\d+}|(Commits in .*$))",
"match": "^(Untracked files|((Unstaged|Staged) changes)|Stashes|Recent commits|Unmerged into|Unpushed to|Unpulled from|HEAD|Branches|Remote|Tags|Pull Requests|Issues|GitError!|(Merging .*(?= \\())|(Rebasing .* onto .*$)|Cherry Picking|Reverting|Stash@{\\d+}|(Commits in .*$))|Changes",
"name": "strong keyword.operator.new.section.header.magit magit.header"
},
"highlight": {