From ef9d439695a3a47b6cba4b2190d7a34b6b1f6d6f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 12 May 2017 21:40:22 -0400 Subject: [PATCH 01/13] Access a Repository's Cache --- lib/models/repository-states/present.js | 5 +++++ lib/models/repository-states/state.js | 7 +++++++ lib/models/repository.js | 1 + 3 files changed, 13 insertions(+) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 5d082af27d..590a24a240 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -695,6 +695,11 @@ export default class Present extends State { getLastHistorySnapshots(partialDiscardFilePath = null) { return this.discardHistory.getLastSnapshots(partialDiscardFilePath); } + + // Cache + getCache() { + return this.cache; + } } State.register(Present); diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 7008d2d246..6ac1a85cca 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -448,6 +448,13 @@ export default class State { return ''; } + // Cache + + @shouldDelegate + getCache() { + return null; + } + // Internal ////////////////////////////////////////////////////////////////////////////////////////////////////////// // Non-delegated methods that provide subclasses with convenient access to Repository properties. diff --git a/lib/models/repository.js b/lib/models/repository.js index 9bfc75b187..e09bac85cd 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -347,6 +347,7 @@ const delegates = [ 'setCommitMessage', 'getCommitMessage', + 'getCache', ]; for (let i = 0; i < delegates.length; i++) { From 1d825fbe2fe489618614ee34223d3ca2cade493a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 12 May 2017 21:45:24 -0400 Subject: [PATCH 02/13] Emit didUpdate events from the Cache --- lib/models/repository-states/present.js | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 590a24a240..29c0b9018d 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -1,4 +1,5 @@ import path from 'path'; +import {Emitter} from 'event-kit'; import fs from 'fs-extra'; import State from './state'; @@ -87,6 +88,11 @@ export default class Present extends State { return true; } + destroy() { + this.cache.dispose(); + super.destroy(); + } + showStatusBarTiles() { return true; } @@ -823,6 +829,8 @@ class Cache { constructor() { this.storage = new Map(); this.byGroup = new Map(); + + this.emitter = new Emitter(); } getOrSet(key, operation) { @@ -847,6 +855,8 @@ class Cache { groupSet.add(key); } + this.didUpdate(); + return created; } @@ -854,6 +864,10 @@ class Cache { for (let i = 0; i < keys.length; i++) { keys[i].removeFromCache(this); } + + if (keys.length > 0) { + this.didUpdate(); + } } keysInGroup(group) { @@ -862,16 +876,31 @@ class Cache { removePrimary(primary) { this.storage.delete(primary); + this.didUpdate(); } removeFromGroup(group, key) { const groupSet = this.byGroup.get(group); groupSet && groupSet.delete(key); + this.didUpdate(); } clear() { this.storage.clear(); this.byGroup.clear(); + this.didUpdate(); + } + + didUpdate() { + this.emitter.emit('did-update'); + } + + onDidUpdate(callback) { + return this.emitter.on('did-update', callback); + } + + destroy() { + this.emitter.dispose(); } } From 1d5ea57836fbb99b58d32067149821c4acc2e4ef Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 13 May 2017 15:03:58 -0400 Subject: [PATCH 03/13] Collect cache entry hit and age data for a cache view PaneItem --- lib/controllers/root-controller.js | 22 ++++ lib/models/repository-states/present.js | 13 ++- lib/views/git-cache-view.js | 139 ++++++++++++++++++++++++ styles/cache-view.less | 45 ++++++++ 4 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 lib/views/git-cache-view.js create mode 100644 styles/cache-view.less diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index eba57e1967..64f0c6b3d3 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -22,6 +22,7 @@ import GitTabController from './git-tab-controller'; import StatusBarTileController from './status-bar-tile-controller'; import RepositoryConflictController from './repository-conflict-controller'; import GithubLoginModel from '../models/github-login-model'; +import GitCacheView from '../views/git-cache-view'; import Conflict from '../models/conflicts/conflict'; import RefHolder from '../models/ref-holder'; import Switchboard from '../switchboard'; @@ -88,6 +89,7 @@ export default class RootController extends React.Component { initDialogPath: null, initDialogResolve: null, credentialDialogQuery: null, + cacheViewActive: false, }; this.refGitTabController = new RefHolder(); @@ -120,6 +122,7 @@ export default class RootController extends React.Component { {devMode && } + @@ -149,6 +152,7 @@ export default class RootController extends React.Component { {this.renderCloneDialog()} {this.renderCredentialDialog()} {this.renderOpenIssueishDialog()} + {this.renderGitCacheView()} {this.renderRepositoryConflictController()} {this.renderFilePatches()} @@ -362,6 +366,19 @@ export default class RootController extends React.Component { removeFilePatchItem(item) { return this.props.removeFilePatchItem(item); } + renderGitCacheView() { + if (!this.state.cacheViewActive) { + return null; + } + + return ( + { this.setState({cacheViewActive: false}); }}> + + + ); + } componentWillUnmount() { this.subscription.dispose(); @@ -414,6 +431,11 @@ export default class RootController extends React.Component { this.props.workspace.open('atom-github://debug/timings'); } + @autobind + showCacheDiagnostics() { + this.setState({cacheViewActive: true}); + } + @autobind async acceptClone(remoteUrl, projectPath) { this.setState({cloneDialogInProgress: true}); diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 29c0b9018d..c54b01a697 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -837,12 +837,17 @@ class Cache { const primary = key.getPrimary(); const existing = this.storage.get(primary); if (existing !== undefined) { - return existing; + existing.hits++; + return existing.promise; } const created = operation(); - this.storage.set(primary, created); + this.storage.set(primary, { + createdAt: performance.now(), + hits: 0, + promise: created, + }); const groups = key.getGroups(); for (let i = 0; i < groups.length; i++) { @@ -885,6 +890,10 @@ class Cache { this.didUpdate(); } + [Symbol.iterator]() { + return this.storage[Symbol.iterator](); + } + clear() { this.storage.clear(); this.byGroup.clear(); diff --git a/lib/views/git-cache-view.js b/lib/views/git-cache-view.js new file mode 100644 index 0000000000..1f94f16fa0 --- /dev/null +++ b/lib/views/git-cache-view.js @@ -0,0 +1,139 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {autobind} from 'core-decorators'; +import {inspect} from 'util'; + +import ObserveModel from '../views/observe-model'; + +export default class GitCacheView extends React.Component { + static propTypes = { + repository: PropTypes.object.isRequired, + } + + getURI() { + return 'atom-github://debug/cache'; + } + + getTitle() { + return 'GitHub Package Cache View'; + } + + serialize() { + return null; + } + + @autobind + fetchRepositoryData(repository) { + return repository.getCache(); + } + + @autobind + fetchCacheData(cache) { + const cached = {}; + const promises = []; + const now = performance.now(); + + for (const [key, value] of cache) { + cached[key] = { + hits: value.hits, + age: now - value.createdAt, + }; + + promises.push( + value.promise + .then( + payload => inspect(payload, {breakLength: 50}), + err => `${err.message}\n${err.stack}`, + ) + .then(resolved => { cached[key].value = resolved; }), + ); + } + + return Promise.all(promises).then(() => cached); + } + + render() { + return ( + + {cache => ( + + {this.renderCache} + + )} + + ); + } + + @autobind + renderCache(contents) { + const keys = Object.keys(contents || []); + + return ( +
+
+

Cache contents

+

+ {keys.length} cached items +

+
+
+ + + + + + + + + + + {keys.map(key => ( + + + + + + + ))} + +
keyagehitscontent
+ {key} + + {this.formatAge(contents[key].age)} + + {contents[key].hits} + + {contents[key].value} +
+
+
+ ); + } + + formatAge(ageMs) { + let remaining = ageMs; + const parts = []; + + if (remaining > 3600000) { + const hours = Math.floor(remaining / 3600000); + parts.push(`${hours}h`); + remaining -= (3600000 * hours); + } + + if (remaining > 60000) { + const minutes = Math.floor(remaining / 60000); + parts.push(`${minutes}m`); + remaining -= (60000 * minutes); + } + + if (remaining > 1000) { + const seconds = Math.floor(remaining / 1000); + parts.push(`${seconds}s`); + remaining -= (1000 * seconds); + } + + parts.push(`${Math.floor(remaining)}ms`); + + return parts.slice(parts.length - 2).join(' '); + } +} diff --git a/styles/cache-view.less b/styles/cache-view.less new file mode 100644 index 0000000000..dc0df18316 --- /dev/null +++ b/styles/cache-view.less @@ -0,0 +1,45 @@ +@import "variables"; + +.github-CacheView { + width: 100%; + height: 100%; + overflow-y: auto; + + header { + text-align: center; + border-bottom: 1px solid @base-border-color; + padding: @component-padding; + } + + table { + margin: 10px 20px; + width: 90%; + } + + thead { + color: @text-color-highlight; + background-color: @background-color-highlight; + font-weight: bold; + } + + td { + padding: @component-padding / 2; + vertical-align: top; + } + + &-Key { + text-align: right; + } + + &-Age { + min-width: 100px; + } + + &-Hits { + min-width: 50px; + } + + &-Content { + white-space: pre-wrap; + } +} From 1f42569da3ecd68ebb4d6d148729d7938797800a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 15 May 2017 11:51:12 -0400 Subject: [PATCH 04/13] Configurable sort orders for cache entries --- lib/views/git-cache-view.js | 59 ++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/lib/views/git-cache-view.js b/lib/views/git-cache-view.js index 1f94f16fa0..98a886ddec 100644 --- a/lib/views/git-cache-view.js +++ b/lib/views/git-cache-view.js @@ -5,11 +5,27 @@ import {inspect} from 'util'; import ObserveModel from '../views/observe-model'; +const sortOrders = { + 'by key': (a, b) => a.key.localeCompare(b.key), + 'oldest first': (a, b) => b.age - a.age, + 'newest first': (a, b) => a.age - b.age, + 'most hits': (a, b) => b.hits - a.hits, + 'fewest hits': (a, b) => a.hits - b.hits, +}; + export default class GitCacheView extends React.Component { static propTypes = { repository: PropTypes.object.isRequired, } + constructor(props, context) { + super(props, context); + + this.state = { + order: 'by key', + }; + } + getURI() { return 'atom-github://debug/cache'; } @@ -66,17 +82,39 @@ export default class GitCacheView extends React.Component { @autobind renderCache(contents) { - const keys = Object.keys(contents || []); + const rows = Object.keys(contents || {}).map(key => { + return { + key, + age: contents[key].age, + hits: contents[key].hits, + content: contents[key].value, + }; + }); + + rows.sort(sortOrders[this.state.order]); + + const orders = Object.keys(sortOrders); return (

Cache contents

- {keys.length} cached items + {rows.length} cached items

+

+ sort + +

@@ -87,19 +125,19 @@ export default class GitCacheView extends React.Component { - {keys.map(key => ( - + {rows.map(row => ( + ))} @@ -136,4 +174,9 @@ export default class GitCacheView extends React.Component { return parts.slice(parts.length - 2).join(' '); } + + @autobind + didSelectItem(event) { + this.setState({order: event.target.value}); + } } From 7dabbf1d8379c0c326d8fe412fdaace3a79288b9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 15 May 2017 14:04:58 -0400 Subject: [PATCH 05/13] Cache.destroy() --- lib/models/repository-states/present.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index c54b01a697..b1afac9a99 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -89,7 +89,7 @@ export default class Present extends State { } destroy() { - this.cache.dispose(); + this.cache.destroy(); super.destroy(); } From 97fe672d4ee209088a316dfb8be07100ce5c5396 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 15 May 2017 14:26:50 -0400 Subject: [PATCH 06/13] Clear button --- lib/views/git-cache-view.js | 39 ++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/views/git-cache-view.js b/lib/views/git-cache-view.js index 98a886ddec..9813e1f746 100644 --- a/lib/views/git-cache-view.js +++ b/lib/views/git-cache-view.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import {autobind} from 'core-decorators'; import {inspect} from 'util'; -import ObserveModel from '../views/observe-model'; +import ObserveModel from './observe-model'; const sortOrders = { 'by key': (a, b) => a.key.localeCompare(b.key), @@ -104,16 +104,23 @@ export default class GitCacheView extends React.Component {

-

- sort - +

+ + order + + + + +

- {key} + {row.key} - {this.formatAge(contents[key].age)} + {this.formatAge(row.age)} - {contents[key].hits} + {row.hits} - {contents[key].value} + {row.content}
@@ -179,4 +186,14 @@ export default class GitCacheView extends React.Component { didSelectItem(event) { this.setState({order: event.target.value}); } + + @autobind + clearCache() { + const cache = this.props.repository.getCache(); + if (!cache) { + return; + } + + cache.clear(); + } } From 694cbd052c28ab59c3ad80672731313cca476e45 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 15 May 2017 14:26:56 -0400 Subject: [PATCH 07/13] :art: --- styles/cache-view.less | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/styles/cache-view.less b/styles/cache-view.less index dc0df18316..3f34f12d52 100644 --- a/styles/cache-view.less +++ b/styles/cache-view.less @@ -11,6 +11,15 @@ padding: @component-padding; } + &-Controls { + padding: 0 @component-padding; + text-align: right; + } + + &-Clear { + margin-left: 1em; + } + table { margin: 10px 20px; width: 90%; From 9ee19cd06a1b5eee2d2b2226b117fd1ab02576b7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 16 May 2017 10:43:56 -0400 Subject: [PATCH 08/13] Remove individual items from the cache --- lib/views/git-cache-view.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/views/git-cache-view.js b/lib/views/git-cache-view.js index 9813e1f746..97302e8ea1 100644 --- a/lib/views/git-cache-view.js +++ b/lib/views/git-cache-view.js @@ -135,7 +135,9 @@ export default class GitCacheView extends React.Component { {rows.map(row => ( ))} diff --git a/styles/cache-view.less b/styles/cache-view.less index f05574d9d9..0847e9aff5 100644 --- a/styles/cache-view.less +++ b/styles/cache-view.less @@ -20,6 +20,10 @@ margin-left: 1em; } + &-Order select { + margin-left: 0.5em; + } + table { margin: 10px 20px; width: 90%; @@ -40,6 +44,7 @@ button { width: 100%; font-family: monospace; + text-align: left; } } @@ -53,5 +58,7 @@ &-Content { white-space: pre-wrap; + max-height: 100px; + overflow-y: scroll; } }
- {row.key} + {this.formatAge(row.age)} @@ -187,6 +189,15 @@ export default class GitCacheView extends React.Component { this.setState({order: event.target.value}); } + didClickKey(key) { + const cache = this.props.repository.getCache(); + if (!cache) { + return; + } + + cache.removePrimary(key); + } + @autobind clearCache() { const cache = this.props.repository.getCache(); From 51f5e58b99403b5b79675fd2d1a2c9493a013d55 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 16 May 2017 10:49:33 -0400 Subject: [PATCH 09/13] CSS for buttons --- styles/cache-view.less | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/styles/cache-view.less b/styles/cache-view.less index 3f34f12d52..f05574d9d9 100644 --- a/styles/cache-view.less +++ b/styles/cache-view.less @@ -37,7 +37,10 @@ } &-Key { - text-align: right; + button { + width: 100%; + font-family: monospace; + } } &-Age { From b95edc912fee927ddac74ed69ab8ef0cdfd37ce7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 1 May 2018 14:30:58 -0400 Subject: [PATCH 10/13] Use the new PaneItem opener support --- lib/controllers/root-controller.js | 19 ++++--------------- lib/views/git-cache-view.js | 6 ++++++ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 5a1d7c9471..100879c463 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -346,6 +346,9 @@ export default class RootController extends React.Component { {({itemHolder}) => } + + {({itemHolder}) => } + ); } @@ -364,20 +367,6 @@ export default class RootController extends React.Component { return this.props.getRepositoryForWorkdir(workdir); } - renderGitCacheView() { - if (!this.state.cacheViewActive) { - return null; - } - - return ( - { this.setState({cacheViewActive: false}); }}> - - - ); - } - componentWillUnmount() { this.subscription.dispose(); } @@ -423,7 +412,7 @@ export default class RootController extends React.Component { @autobind showCacheDiagnostics() { - this.setState({cacheViewActive: true}); + this.props.workspace.open(GitCacheView.buildURI()); } @autobind diff --git a/lib/views/git-cache-view.js b/lib/views/git-cache-view.js index 97302e8ea1..b8fbb25209 100644 --- a/lib/views/git-cache-view.js +++ b/lib/views/git-cache-view.js @@ -14,6 +14,12 @@ const sortOrders = { }; export default class GitCacheView extends React.Component { + static uriPattern = 'atom-github://debug/cache' + + static buildURI() { + return this.uriPattern; + } + static propTypes = { repository: PropTypes.object.isRequired, } From f25011d95f49024c9cbc46decc851de9d19da39d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 1 May 2018 14:32:42 -0400 Subject: [PATCH 11/13] Remove unused RootController state --- lib/controllers/root-controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 100879c463..215770f173 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -73,7 +73,6 @@ export default class RootController extends React.Component { initDialogPath: null, initDialogResolve: null, credentialDialogQuery: null, - cacheViewActive: false, }; this.refGitTabController = new RefHolder(); From a9cfcbecf933acdd6ca95c69bd4d586af9811744 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 1 May 2018 14:49:33 -0400 Subject: [PATCH 12/13] Custom inspect functions --- lib/models/branch-set.js | 6 ++++++ lib/models/branch.js | 6 ++++++ lib/views/git-cache-view.js | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/models/branch-set.js b/lib/models/branch-set.js index 94371cb033..afd8c41df0 100644 --- a/lib/models/branch-set.js +++ b/lib/models/branch-set.js @@ -1,3 +1,5 @@ +import util from 'util'; + import {nullBranch} from './branch'; function pushAtKey(map, key, value) { @@ -60,4 +62,8 @@ export default class BranchSet { getPushSources(remoteName, remoteRefName) { return this.byPushRef.get(`${remoteName}\0${remoteRefName}`) || []; } + + [util.inspect.custom](depth, options) { + return `BranchSet {${util.inspect(this.all)}}`; + } } diff --git a/lib/models/branch.js b/lib/models/branch.js index aba10e3f20..2f7307cfee 100644 --- a/lib/models/branch.js +++ b/lib/models/branch.js @@ -1,3 +1,5 @@ +import util from 'util'; + const DETACHED = Symbol('detached'); const REMOTE_TRACKING = Symbol('remote-tracking'); @@ -132,4 +134,8 @@ export const nullBranch = { isPresent() { return false; }, + + [util.inspect.custom](depth, options) { + return '{nullBranch}'; + }, }; diff --git a/lib/views/git-cache-view.js b/lib/views/git-cache-view.js index b8fbb25209..bb40baabe5 100644 --- a/lib/views/git-cache-view.js +++ b/lib/views/git-cache-view.js @@ -64,7 +64,7 @@ export default class GitCacheView extends React.Component { promises.push( value.promise .then( - payload => inspect(payload, {breakLength: 50}), + payload => inspect(payload, {depth: 3, breakLength: 30}), err => `${err.message}\n${err.stack}`, ) .then(resolved => { cached[key].value = resolved; }), From b0b4cf0f75dec3538ff76c4ccdbf59477f96a29b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 1 May 2018 15:02:59 -0400 Subject: [PATCH 13/13] Tiny bit of styling --- lib/views/git-cache-view.js | 2 +- styles/cache-view.less | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/views/git-cache-view.js b/lib/views/git-cache-view.js index bb40baabe5..0998b8ca85 100644 --- a/lib/views/git-cache-view.js +++ b/lib/views/git-cache-view.js @@ -152,7 +152,7 @@ export default class GitCacheView extends React.Component { {row.hits} - {row.content} + {row.content}