Skip to content

Commit 5ae52b6

Browse files
author
Benjamin Pasero
committed
files - implement ctime properly as btime (fix microsoft#84525)
1 parent d5ff86c commit 5ae52b6

8 files changed

Lines changed: 74 additions & 11 deletions

File tree

extensions/vscode-api-tests/src/singlefolder-tests/workspace.fs.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ suite('workspace-fs', () => {
2323
assert.equal(typeof stat.mtime, 'number');
2424
assert.equal(typeof stat.ctime, 'number');
2525

26+
assert.ok(stat.mtime > 0);
27+
assert.ok(stat.ctime > 0);
2628

2729
const entries = await vscode.workspace.fs.readDirectory(root);
2830
assert.ok(entries.length > 0);

src/vs/platform/files/common/fileService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ export class FileService extends Disposable implements IFileService {
209209
});
210210
}
211211

212+
private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial<IStat>, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise<IFileStat>;
213+
private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat, siblings: number | undefined, resolveMetadata: true, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise<IFileStatWithMetadata>;
212214
private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial<IStat>, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise<IFileStat> {
213215

214216
// convert to file stat
@@ -219,6 +221,7 @@ export class FileService extends Disposable implements IFileService {
219221
isSymbolicLink: (stat.type & FileType.SymbolicLink) !== 0,
220222
isReadonly: !!(provider.capabilities & FileSystemProviderCapabilities.Readonly),
221223
mtime: stat.mtime,
224+
ctime: stat.ctime,
222225
size: stat.size,
223226
etag: etag({ mtime: stat.mtime, size: stat.size })
224227
};

src/vs/platform/files/common/files.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,17 @@ export enum FileType {
207207

208208
export interface IStat {
209209
type: FileType;
210+
211+
/**
212+
* The last modification date represented as millis from unix epoch.
213+
*/
210214
mtime: number;
215+
216+
/**
217+
* The creation date represented as millis from unix epoch.
218+
*/
211219
ctime: number;
220+
212221
size: number;
213222
}
214223

@@ -583,14 +592,21 @@ interface IBaseStat {
583592
size?: number;
584593

585594
/**
586-
* The last modification date represented
587-
* as millis from unix epoch.
595+
* The last modification date represented as millis from unix epoch.
588596
*
589597
* The value may or may not be resolved as
590598
* it is optional.
591599
*/
592600
mtime?: number;
593601

602+
/**
603+
* The creation date represented as millis from unix epoch.
604+
*
605+
* The value may or may not be resolved as
606+
* it is optional.
607+
*/
608+
ctime?: number;
609+
594610
/**
595611
* A unique identifier thet represents the
596612
* current state of the file or directory.
@@ -608,6 +624,7 @@ interface IBaseStat {
608624

609625
export interface IBaseStatWithMetadata extends IBaseStat {
610626
mtime: number;
627+
ctime: number;
611628
etag: string;
612629
size: number;
613630
}
@@ -635,6 +652,7 @@ export interface IFileStat extends IBaseStat {
635652

636653
export interface IFileStatWithMetadata extends IFileStat, IBaseStatWithMetadata {
637654
mtime: number;
655+
ctime: number;
638656
etag: string;
639657
size: number;
640658
children?: IFileStatWithMetadata[];
@@ -703,7 +721,7 @@ export interface IResolveFileOptions {
703721
readonly resolveSingleChildDescendants?: boolean;
704722

705723
/**
706-
* Will resolve mtime, size and etag of files if enabled. This can have a negative impact
724+
* Will resolve mtime, ctime, size and etag of files if enabled. This can have a negative impact
707725
* on performance and thus should only be used when these values are required.
708726
*/
709727
readonly resolveMetadata?: boolean;

src/vs/platform/files/node/diskFileSystemProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export class DiskFileSystemProvider extends Disposable implements
8080

8181
return {
8282
type: this.toType(stat, isSymbolicLink),
83-
ctime: stat.ctime.getTime(),
83+
ctime: stat.birthtime.getTime(), // intentionally not using ctime here, we want the creation time
8484
mtime: stat.mtime.getTime(),
8585
size: stat.size
8686
};

src/vs/platform/files/test/node/diskFileService.test.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -213,23 +213,32 @@ suite('Disk File Service', function () {
213213
assert.equal(exists, false);
214214
});
215215

216-
test('resolve', async () => {
217-
const resolved = await service.resolve(URI.file(testDir), { resolveTo: [URI.file(join(testDir, 'deep'))] });
218-
assert.equal(resolved.children!.length, 8);
216+
test('resolve - file', async () => {
217+
const resource = URI.file(getPathFromAmdModule(require, './fixtures/resolver/index.html'));
218+
const resolved = await service.resolve(resource);
219219

220-
const deep = (getByName(resolved, 'deep')!);
221-
assert.equal(deep.children!.length, 4);
220+
assert.equal(resolved.name, 'index.html');
221+
assert.equal(resolved.resource.toString(), resource.toString());
222+
assert.equal(resolved.children, undefined);
223+
assert.ok(resolved.mtime! > 0);
224+
assert.ok(resolved.ctime! > 0);
225+
assert.ok(resolved.size! > 0);
222226
});
223227

224228
test('resolve - directory', async () => {
225229
const testsElements = ['examples', 'other', 'index.html', 'site.css'];
226230

227-
const result = await service.resolve(URI.file(getPathFromAmdModule(require, './fixtures/resolver')));
231+
const resource = URI.file(getPathFromAmdModule(require, './fixtures/resolver'));
232+
const result = await service.resolve(resource);
228233

229234
assert.ok(result);
235+
assert.equal(result.resource.toString(), resource.toString());
236+
assert.equal(result.name, 'resolver');
230237
assert.ok(result.children);
231238
assert.ok(result.children!.length > 0);
232239
assert.ok(result!.isDirectory);
240+
assert.ok(result.mtime! > 0);
241+
assert.ok(result.ctime! > 0);
233242
assert.equal(result.children!.length, testsElements.length);
234243

235244
assert.ok(result.children!.every(entry => {
@@ -242,12 +251,18 @@ suite('Disk File Service', function () {
242251
assert.ok(basename(value.resource.fsPath));
243252
if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) {
244253
assert.ok(value.isDirectory);
254+
assert.equal(value.mtime, undefined);
255+
assert.equal(value.ctime, undefined);
245256
} else if (basename(value.resource.fsPath) === 'index.html') {
246257
assert.ok(!value.isDirectory);
247258
assert.ok(!value.children);
259+
assert.equal(value.mtime, undefined);
260+
assert.equal(value.ctime, undefined);
248261
} else if (basename(value.resource.fsPath) === 'site.css') {
249262
assert.ok(!value.isDirectory);
250263
assert.ok(!value.children);
264+
assert.equal(value.mtime, undefined);
265+
assert.equal(value.ctime, undefined);
251266
} else {
252267
assert.ok(!'Unexpected value ' + basename(value.resource.fsPath));
253268
}
@@ -260,9 +275,12 @@ suite('Disk File Service', function () {
260275
const result = await service.resolve(URI.file(getPathFromAmdModule(require, './fixtures/resolver')), { resolveMetadata: true });
261276

262277
assert.ok(result);
278+
assert.equal(result.name, 'resolver');
263279
assert.ok(result.children);
264280
assert.ok(result.children!.length > 0);
265281
assert.ok(result!.isDirectory);
282+
assert.ok(result.mtime! > 0);
283+
assert.ok(result.ctime! > 0);
266284
assert.equal(result.children!.length, testsElements.length);
267285

268286
assert.ok(result.children!.every(entry => {
@@ -277,18 +295,32 @@ suite('Disk File Service', function () {
277295
assert.ok(basename(value.resource.fsPath));
278296
if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) {
279297
assert.ok(value.isDirectory);
298+
assert.ok(value.mtime! > 0);
299+
assert.ok(value.ctime! > 0);
280300
} else if (basename(value.resource.fsPath) === 'index.html') {
281301
assert.ok(!value.isDirectory);
282302
assert.ok(!value.children);
303+
assert.ok(value.mtime! > 0);
304+
assert.ok(value.ctime! > 0);
283305
} else if (basename(value.resource.fsPath) === 'site.css') {
284306
assert.ok(!value.isDirectory);
285307
assert.ok(!value.children);
308+
assert.ok(value.mtime! > 0);
309+
assert.ok(value.ctime! > 0);
286310
} else {
287311
assert.ok(!'Unexpected value ' + basename(value.resource.fsPath));
288312
}
289313
});
290314
});
291315

316+
test('resolve - directory with resolveTo', async () => {
317+
const resolved = await service.resolve(URI.file(testDir), { resolveTo: [URI.file(join(testDir, 'deep'))] });
318+
assert.equal(resolved.children!.length, 8);
319+
320+
const deep = (getByName(resolved, 'deep')!);
321+
assert.equal(deep.children!.length, 4);
322+
});
323+
292324
test('resolve - directory - resolveTo single directory', async () => {
293325
const resolverFixturesPath = getPathFromAmdModule(require, './fixtures/resolver');
294326
const result = await service.resolve(URI.file(resolverFixturesPath), { resolveTo: [URI.file(join(resolverFixturesPath, 'other/deep'))] });

src/vs/workbench/api/browser/mainThreadFileSystem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
5252
$stat(uri: UriComponents): Promise<IStat> {
5353
return this._fileService.resolve(URI.revive(uri), { resolveMetadata: true }).then(stat => {
5454
return {
55-
ctime: 0,
55+
ctime: stat.ctime,
5656
mtime: stat.mtime,
5757
size: stat.size,
5858
type: MainThreadFileSystem._getFileType(stat)

src/vs/workbench/services/textfile/common/textFileEditorModel.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { Schemas } from 'vs/base/common/network';
3232

3333
export interface IBackupMetaData {
3434
mtime: number;
35+
ctime: number;
3536
size: number;
3637
etag: string;
3738
orphaned: boolean;
@@ -224,6 +225,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
224225
if (isEqual(target, this.resource) && this.lastResolvedFileStat) {
225226
meta = {
226227
mtime: this.lastResolvedFileStat.mtime,
228+
ctime: this.lastResolvedFileStat.ctime,
227229
size: this.lastResolvedFileStat.size,
228230
etag: this.lastResolvedFileStat.etag,
229231
orphaned: this.inOrphanMode
@@ -313,6 +315,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
313315
resource: this.resource,
314316
name: basename(this.resource),
315317
mtime: resolvedBackup.meta ? resolvedBackup.meta.mtime : Date.now(),
318+
ctime: resolvedBackup.meta ? resolvedBackup.meta.ctime : Date.now(),
316319
size: resolvedBackup.meta ? resolvedBackup.meta.size : 0,
317320
etag: resolvedBackup.meta ? resolvedBackup.meta.etag : ETAG_DISABLED, // etag disabled if unknown!
318321
value: resolvedBackup.value,
@@ -397,6 +400,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
397400
resource: this.resource,
398401
name: content.name,
399402
mtime: content.mtime,
403+
ctime: content.ctime,
400404
size: content.size,
401405
etag: content.etag,
402406
isDirectory: false,

src/vs/workbench/test/workbenchTestServices.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ export class TestTextFileService extends NativeTextFileService {
262262
resource: content.resource,
263263
name: content.name,
264264
mtime: content.mtime,
265+
ctime: content.ctime,
265266
etag: content.etag,
266267
encoding: 'utf8',
267268
value: await createTextBufferFactoryFromStream(content.value),
@@ -996,6 +997,7 @@ export class TestFileService implements IFileService {
996997
etag: 'index.txt',
997998
encoding: 'utf8',
998999
mtime: Date.now(),
1000+
ctime: Date.now(),
9991001
name: resources.basename(resource),
10001002
size: 1
10011003
});
@@ -1022,6 +1024,7 @@ export class TestFileService implements IFileService {
10221024
etag: 'index.txt',
10231025
encoding: 'utf8',
10241026
mtime: Date.now(),
1027+
ctime: Date.now(),
10251028
size: 1,
10261029
name: resources.basename(resource)
10271030
});
@@ -1033,6 +1036,7 @@ export class TestFileService implements IFileService {
10331036
etag: 'index.txt',
10341037
encoding: 'utf8',
10351038
mtime: Date.now(),
1039+
ctime: Date.now(),
10361040
size: 42,
10371041
isDirectory: false,
10381042
name: resources.basename(resource)

0 commit comments

Comments
 (0)