Skip to content

Commit 37fbae0

Browse files
committed
Merge commit 'refs/pull/64826/head' of github.com:Microsoft/vscode into pr/64826
2 parents eb69d48 + 04c3dde commit 37fbae0

5 files changed

Lines changed: 248 additions & 28 deletions

File tree

extensions/git/src/api/api1.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { Model } from '../model';
77
import { Repository as BaseRepository, Resource } from '../repository';
8-
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status } from './git';
8+
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions } from './git';
99
import { Event, SourceControlInputBox, Uri, SourceControl } from 'vscode';
1010
import { mapEvent } from '../util';
1111

@@ -76,6 +76,10 @@ export class ApiRepository implements Repository {
7676
return this._repository.setConfig(key, value);
7777
}
7878

79+
getGlobalConfig(key: string): Promise<string> {
80+
return this._repository.getGlobalConfig(key);
81+
}
82+
7983
getObjectDetails(treeish: string, path: string): Promise<{ mode: string; object: string; size: number; }> {
8084
return this._repository.getObjectDetails(treeish, path);
8185
}
@@ -104,27 +108,37 @@ export class ApiRepository implements Repository {
104108
return this._repository.diff(cached);
105109
}
106110

107-
diffWithHEAD(path: string): Promise<string> {
111+
diffWithHEAD(): Promise<Change[]>;
112+
diffWithHEAD(path: string): Promise<string>;
113+
diffWithHEAD(path?: string): Promise<string | Change[]> {
108114
return this._repository.diffWithHEAD(path);
109115
}
110116

111-
diffWith(ref: string, path: string): Promise<string> {
117+
diffWith(ref: string): Promise<Change[]>;
118+
diffWith(ref: string, path: string): Promise<string>;
119+
diffWith(ref: string, path?: string): Promise<string | Change[]> {
112120
return this._repository.diffWith(ref, path);
113121
}
114122

115-
diffIndexWithHEAD(path: string): Promise<string> {
123+
diffIndexWithHEAD(): Promise<Change[]>;
124+
diffIndexWithHEAD(path: string): Promise<string>;
125+
diffIndexWithHEAD(path?: string): Promise<string | Change[]> {
116126
return this._repository.diffIndexWithHEAD(path);
117127
}
118128

119-
diffIndexWith(ref: string, path: string): Promise<string> {
129+
diffIndexWith(ref: string): Promise<Change[]>;
130+
diffIndexWith(ref: string, path: string): Promise<string>;
131+
diffIndexWith(ref: string, path?: string): Promise<string | Change[]> {
120132
return this._repository.diffIndexWith(ref, path);
121133
}
122134

123135
diffBlobs(object1: string, object2: string): Promise<string> {
124136
return this._repository.diffBlobs(object1, object2);
125137
}
126138

127-
diffBetween(ref1: string, ref2: string, path: string): Promise<string> {
139+
diffBetween(ref1: string, ref2: string): Promise<Change[]>;
140+
diffBetween(ref1: string, ref2: string, path: string): Promise<string>;
141+
diffBetween(ref1: string, ref2: string, path?: string): Promise<string | Change[]> {
128142
return this._repository.diffBetween(ref1, ref2, path);
129143
}
130144

@@ -183,6 +197,10 @@ export class ApiRepository implements Repository {
183197
blame(path: string): Promise<string> {
184198
return this._repository.blame(path);
185199
}
200+
201+
log(options?: LogOptions): Promise<Commit[]> {
202+
return this._repository.log(options);
203+
}
186204
}
187205

188206
export class ApiGit implements Git {

extensions/git/src/api/git.d.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface Commit {
4141
readonly hash: string;
4242
readonly message: string;
4343
readonly parents: string[];
44+
readonly authorEmail?: string | undefined;
4445
}
4546

4647
export interface Submodule {
@@ -110,6 +111,14 @@ export interface RepositoryUIState {
110111
readonly onDidChange: Event<void>;
111112
}
112113

114+
/**
115+
* Log options.
116+
*/
117+
export interface LogOptions {
118+
/** Max number of log entries to retrieve. If not specified, the default is 32. */
119+
readonly maxEntries?: number;
120+
}
121+
113122
export interface Repository {
114123

115124
readonly rootUri: Uri;
@@ -120,6 +129,7 @@ export interface Repository {
120129
getConfigs(): Promise<{ key: string; value: string; }[]>;
121130
getConfig(key: string): Promise<string>;
122131
setConfig(key: string, value: string): Promise<string>;
132+
getGlobalConfig(key: string): Promise<string>;
123133

124134
getObjectDetails(treeish: string, path: string): Promise<{ mode: string, object: string, size: number }>;
125135
detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }>;
@@ -131,11 +141,16 @@ export interface Repository {
131141

132142
apply(patch: string, reverse?: boolean): Promise<void>;
133143
diff(cached?: boolean): Promise<string>;
144+
diffWithHEAD(): Promise<Change[]>;
134145
diffWithHEAD(path: string): Promise<string>;
146+
diffWith(ref: string): Promise<Change[]>;
135147
diffWith(ref: string, path: string): Promise<string>;
148+
diffIndexWithHEAD(): Promise<Change[]>;
136149
diffIndexWithHEAD(path: string): Promise<string>;
150+
diffIndexWith(ref: string): Promise<Change[]>;
137151
diffIndexWith(ref: string, path: string): Promise<string>;
138152
diffBlobs(object1: string, object2: string): Promise<string>;
153+
diffBetween(ref1: string, ref2: string): Promise<Change[]>;
139154
diffBetween(ref1: string, ref2: string, path: string): Promise<string>;
140155

141156
hashObject(data: string): Promise<string>;
@@ -156,7 +171,9 @@ export interface Repository {
156171
fetch(remote?: string, ref?: string, depth?: number): Promise<void>;
157172
pull(unshallow?: boolean): Promise<void>;
158173
push(remoteName?: string, branchName?: string, setUpstream?: boolean): Promise<void>;
174+
159175
blame(path: string): Promise<string>;
176+
log(options?: LogOptions): Promise<Commit[]>;
160177
}
161178

162179
export interface API {

extensions/git/src/git.ts

Lines changed: 167 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import { EventEmitter } from 'events';
1212
import iconv = require('iconv-lite');
1313
import * as filetype from 'file-type';
1414
import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent } from './util';
15-
import { CancellationToken } from 'vscode';
15+
import { CancellationToken, Uri } from 'vscode';
1616
import { detectEncoding } from './encoding';
17-
import { Ref, RefType, Branch, Remote, GitErrorCodes } from './api/git';
17+
import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git';
1818

1919
const readfile = denodeify<string, string | null, string>(fs.readFile);
2020

@@ -311,6 +311,8 @@ function getGitErrorCode(stderr: string): string | undefined {
311311
return undefined;
312312
}
313313

314+
const COMMIT_FORMAT = '%H\n%ae\n%P\n%B';
315+
314316
export class Git {
315317

316318
readonly path: string;
@@ -450,6 +452,7 @@ export interface Commit {
450452
hash: string;
451453
message: string;
452454
parents: string[];
455+
authorEmail?: string | undefined;
453456
}
454457

455458
export class GitStatusParser {
@@ -581,13 +584,13 @@ export function parseGitmodules(raw: string): Submodule[] {
581584
}
582585

583586
export function parseGitCommit(raw: string): Commit | null {
584-
const match = /^([0-9a-f]{40})\n(.*)\n([^]*)$/m.exec(raw.trim());
587+
const match = /^([0-9a-f]{40})\n(.*)\n(.*)\n([^]*)$/m.exec(raw.trim());
585588
if (!match) {
586589
return null;
587590
}
588591

589-
const parents = match[2] ? match[2].split(' ') : [];
590-
return { hash: match[1], message: match[3], parents };
592+
const parents = match[3] ? match[3].split(' ') : [];
593+
return { hash: match[1], message: match[4], parents, authorEmail: match[2] };
591594
}
592595

593596
interface LsTreeElement {
@@ -701,6 +704,41 @@ export class Repository {
701704
});
702705
}
703706

707+
async log(options?: LogOptions): Promise<Commit[]> {
708+
const maxEntries = options && typeof options.maxEntries === 'number' && options.maxEntries > 0 ? options.maxEntries : 32;
709+
const args = ['log', '-' + maxEntries, `--pretty=format:${COMMIT_FORMAT}%x00%x00`];
710+
const gitResult = await this.run(args);
711+
if (gitResult.exitCode) {
712+
// An empty repo.
713+
return [];
714+
}
715+
716+
const s = gitResult.stdout;
717+
const result: Commit[] = [];
718+
let index = 0;
719+
while (index < s.length) {
720+
let nextIndex = s.indexOf('\x00\x00', index);
721+
if (nextIndex === -1) {
722+
nextIndex = s.length;
723+
}
724+
725+
let entry = s.substr(index, nextIndex - index);
726+
if (entry.startsWith('\n')) {
727+
entry = entry.substring(1);
728+
}
729+
730+
const commit = parseGitCommit(entry);
731+
if (!commit) {
732+
break;
733+
}
734+
735+
result.push(commit);
736+
index = nextIndex + 2;
737+
}
738+
739+
return result;
740+
}
741+
704742
async bufferString(object: string, encoding: string = 'utf8', autoGuessEncoding = false): Promise<string> {
705743
const stdout = await this.buffer(object);
706744

@@ -855,25 +893,53 @@ export class Repository {
855893
return result.stdout;
856894
}
857895

858-
async diffWithHEAD(path: string): Promise<string> {
896+
diffWithHEAD(): Promise<Change[]>;
897+
diffWithHEAD(path: string): Promise<string>;
898+
diffWithHEAD(path?: string | undefined): Promise<string | Change[]>;
899+
async diffWithHEAD(path?: string | undefined): Promise<string | Change[]> {
900+
if (!path) {
901+
return await this.diffFiles(false);
902+
}
903+
859904
const args = ['diff', '--', path];
860905
const result = await this.run(args);
861906
return result.stdout;
862907
}
863908

864-
async diffWith(ref: string, path: string): Promise<string> {
909+
diffWith(ref: string): Promise<Change[]>;
910+
diffWith(ref: string, path: string): Promise<string>;
911+
diffWith(ref: string, path?: string | undefined): Promise<string | Change[]>;
912+
async diffWith(ref: string, path?: string): Promise<string | Change[]> {
913+
if (!path) {
914+
return await this.diffFiles(false, ref);
915+
}
916+
865917
const args = ['diff', ref, '--', path];
866918
const result = await this.run(args);
867919
return result.stdout;
868920
}
869921

870-
async diffIndexWithHEAD(path: string): Promise<string> {
922+
diffIndexWithHEAD(): Promise<Change[]>;
923+
diffIndexWithHEAD(path: string): Promise<string>;
924+
diffIndexWithHEAD(path?: string | undefined): Promise<string | Change[]>;
925+
async diffIndexWithHEAD(path?: string): Promise<string | Change[]> {
926+
if (!path) {
927+
return await this.diffFiles(true);
928+
}
929+
871930
const args = ['diff', '--cached', '--', path];
872931
const result = await this.run(args);
873932
return result.stdout;
874933
}
875934

876-
async diffIndexWith(ref: string, path: string): Promise<string> {
935+
diffIndexWith(ref: string): Promise<Change[]>;
936+
diffIndexWith(ref: string, path: string): Promise<string>;
937+
diffIndexWith(ref: string, path?: string | undefined): Promise<string | Change[]>;
938+
async diffIndexWith(ref: string, path?: string): Promise<string | Change[]> {
939+
if (!path) {
940+
return await this.diffFiles(true, ref);
941+
}
942+
877943
const args = ['diff', '--cached', ref, '--', path];
878944
const result = await this.run(args);
879945
return result.stdout;
@@ -885,13 +951,102 @@ export class Repository {
885951
return result.stdout;
886952
}
887953

888-
async diffBetween(ref1: string, ref2: string, path: string): Promise<string> {
889-
const args = ['diff', `${ref1}...${ref2}`, '--', path];
954+
diffBetween(ref1: string, ref2: string): Promise<Change[]>;
955+
diffBetween(ref1: string, ref2: string, path: string): Promise<string>;
956+
diffBetween(ref1: string, ref2: string, path?: string | undefined): Promise<string | Change[]>;
957+
async diffBetween(ref1: string, ref2: string, path?: string): Promise<string | Change[]> {
958+
const range = `${ref1}...${ref2}`;
959+
if (!path) {
960+
return await this.diffFiles(false, range);
961+
}
962+
963+
const args = ['diff', range, '--', path];
890964
const result = await this.run(args);
891965

892966
return result.stdout.trim();
893967
}
894968

969+
private async diffFiles(cached: boolean, ref?: string): Promise<Change[]> {
970+
const args = ['diff', '--name-status', '-z', '--diff-filter=ADMR'];
971+
if (cached) {
972+
args.push('--cached');
973+
}
974+
975+
if (ref) {
976+
args.push(ref);
977+
}
978+
979+
const gitResult = await this.run(args);
980+
if (gitResult.exitCode) {
981+
return [];
982+
}
983+
984+
const entries = gitResult.stdout.split('\x00');
985+
let index = 0;
986+
const result: Change[] = [];
987+
988+
entriesLoop:
989+
while (index < entries.length - 1) {
990+
const change = entries[index++];
991+
const resourcePath = entries[index++];
992+
if (!change || !resourcePath) {
993+
break;
994+
}
995+
996+
const originalUri = Uri.file(path.isAbsolute(resourcePath) ? resourcePath : path.join(this.repositoryRoot, resourcePath));
997+
let status: Status = Status.UNTRACKED;
998+
999+
// Copy or Rename status comes with a number, e.g. 'R100'. We don't need the number, so we use only first character of the status.
1000+
switch (change[0]) {
1001+
case 'M':
1002+
status = Status.MODIFIED;
1003+
break;
1004+
1005+
case 'A':
1006+
status = Status.INDEX_ADDED;
1007+
break;
1008+
1009+
case 'D':
1010+
status = Status.DELETED;
1011+
break;
1012+
1013+
// Rename contains two paths, the second one is what the file is renamed/copied to.
1014+
case 'R':
1015+
if (index >= entries.length) {
1016+
break;
1017+
}
1018+
1019+
const newPath = entries[index++];
1020+
if (!newPath) {
1021+
break;
1022+
}
1023+
1024+
const uri = Uri.file(path.isAbsolute(newPath) ? newPath : path.join(this.repositoryRoot, newPath));
1025+
result.push({
1026+
uri,
1027+
renameUri: uri,
1028+
originalUri,
1029+
status: Status.INDEX_RENAMED
1030+
});
1031+
1032+
continue;
1033+
1034+
default:
1035+
// Unknown status
1036+
break entriesLoop;
1037+
}
1038+
1039+
result.push({
1040+
status,
1041+
originalUri,
1042+
uri: originalUri,
1043+
renameUri: originalUri,
1044+
});
1045+
}
1046+
1047+
return result;
1048+
}
1049+
8951050
async getMergeBase(ref1: string, ref2: string): Promise<string> {
8961051
const args = ['merge-base', ref1, ref2];
8971052
const result = await this.run(args);
@@ -1557,7 +1712,7 @@ export class Repository {
15571712
}
15581713

15591714
async getCommit(ref: string): Promise<Commit> {
1560-
const result = await this.run(['show', '-s', '--format=%H\n%P\n%B', ref]);
1715+
const result = await this.run(['show', '-s', `--format=${COMMIT_FORMAT}`, ref]);
15611716
return parseGitCommit(result.stdout) || Promise.reject<Commit>('bad commit format');
15621717
}
15631718

0 commit comments

Comments
 (0)