Fossil SCM
Include the branch name, if known, in stashed fileinfo objects, and use a longer hash prefix for storing checkin-to-branch mappings (noting that the record does not notice if a checkin is later moved to another branch). (We only know the branch names of leaf checkins we've loaded, but now cache them in persistent storage if possible.) Renamed an internal cache key for consistency.
Commit
8573443f32b446bf1674e4d927f77d2c2bf79eb3b8dd356335b47d4e94345a7c
Parent
aefceac57c98bda…
2 files changed
+35
-8
+10
-4
+35
-8
| --- src/fossil.page.fileedit.js | ||
| +++ src/fossil.page.fileedit.js | ||
| @@ -112,11 +112,11 @@ | ||
| 112 | 112 | and that would be horribly inefficient (meaning "battery-consuming" |
| 113 | 113 | on mobile devices). |
| 114 | 114 | */ |
| 115 | 115 | const $stash = { |
| 116 | 116 | keys: { |
| 117 | - index: F.page.name+':index' | |
| 117 | + index: F.page.name+'/index' | |
| 118 | 118 | }, |
| 119 | 119 | /** |
| 120 | 120 | index: { |
| 121 | 121 | "CHECKIN_HASH:FILENAME": {file info w/o content} |
| 122 | 122 | ... |
| @@ -138,11 +138,30 @@ | ||
| 138 | 138 | by prepending P.name to suffix. */ |
| 139 | 139 | contentKey: function(suffix){return P.name+'/'+suffix}, |
| 140 | 140 | /** Returns the index object, fetching it from the stash or creating |
| 141 | 141 | it anew on the first call. */ |
| 142 | 142 | getIndex: function(){ |
| 143 | - if(!this.index) this.index = F.storage.getJSON(this.keys.index,{}); | |
| 143 | + if(!this.index){ | |
| 144 | + this.index = F.storage.getJSON( | |
| 145 | + this.keys.index, undefined | |
| 146 | + ); | |
| 147 | + if(!this.index){ | |
| 148 | + /*check for and remove/replace older name. This whole block | |
| 149 | + can be removed once the test phase is done (don't want to | |
| 150 | + invalidate the testers' edits on the test server). When | |
| 151 | + doing so, be sure to replace undefined in the above | |
| 152 | + getJSON() call with {}. */ | |
| 153 | + const oldName = F.page.name+':index'; | |
| 154 | + this.index = F.storage.getJSON(oldName,undefined); | |
| 155 | + if(this.index){ | |
| 156 | + F.storage.remove(oldName); | |
| 157 | + this.storeIndex(); | |
| 158 | + }else{ | |
| 159 | + this.index = {}; | |
| 160 | + } | |
| 161 | + } | |
| 162 | + } | |
| 144 | 163 | return this.index; |
| 145 | 164 | }, |
| 146 | 165 | _fireStashEvent: function(){ |
| 147 | 166 | if(this._disableNextEvent) delete this._disableNextEvent; |
| 148 | 167 | else F.page.dispatchEvent('fileedit-stash-updated', this); |
| @@ -173,10 +192,11 @@ | ||
| 173 | 192 | filename: finfo.filename, |
| 174 | 193 | mimetype: finfo.mimetype |
| 175 | 194 | }); |
| 176 | 195 | record.isExe = !!finfo.isExe; |
| 177 | 196 | record.stashTime = new Date().getTime(); |
| 197 | + if(!record.branch) record.branch=finfo.branch; | |
| 178 | 198 | this.storeIndex(); |
| 179 | 199 | if(arguments.length>1){ |
| 180 | 200 | F.storage.set(this.contentKey(key), content); |
| 181 | 201 | } |
| 182 | 202 | this._fireStashEvent(); |
| @@ -256,10 +276,11 @@ | ||
| 256 | 276 | }, |
| 257 | 277 | finfo: {}, |
| 258 | 278 | cache: { |
| 259 | 279 | checkins: undefined, |
| 260 | 280 | files:{}, |
| 281 | + branchKey: 'fileedit/uuid-branches', | |
| 261 | 282 | branchNames: {} |
| 262 | 283 | }, |
| 263 | 284 | /** |
| 264 | 285 | Fetches the list of leaf checkins from the server and updates |
| 265 | 286 | the UI with that list. |
| @@ -281,15 +302,16 @@ | ||
| 281 | 302 | self.cache.checkins = list; |
| 282 | 303 | D.clearElement(D.enable(self.e.selectCi)); |
| 283 | 304 | let loadThisOne; |
| 284 | 305 | list.forEach(function(o,n){ |
| 285 | 306 | if(!n) loadThisOne = o; |
| 286 | - self.cache.branchNames[F.hashDigits(o.checkin)] = o.branch; | |
| 307 | + self.cache.branchNames[F.hashDigits(o.checkin,true)] = o.branch; | |
| 287 | 308 | D.option(self.e.selectCi, o.checkin, |
| 288 | 309 | o.timestamp+' ['+o.branch+']: ' |
| 289 | 310 | +F.hashDigits(o.checkin)); |
| 290 | 311 | }); |
| 312 | + F.storage.setJSON(self.cache.branchKey, self.cache.branchNames); | |
| 291 | 313 | self.loadFiles(loadThisOne ? loadThisOne.checkin : false); |
| 292 | 314 | } |
| 293 | 315 | }); |
| 294 | 316 | }, |
| 295 | 317 | /** |
| @@ -347,18 +369,19 @@ | ||
| 347 | 369 | If this object has ever loaded the given checkin version via |
| 348 | 370 | loadLeaves(), this returns the branch name associated with that |
| 349 | 371 | version, else returns undefined; |
| 350 | 372 | */ |
| 351 | 373 | checkinBranchName: function(uuid){ |
| 352 | - return this.cache.branchNames[F.hashDigits(uuid)]; | |
| 374 | + return this.cache.branchNames[F.hashDigits(uuid,true)]; | |
| 353 | 375 | }, |
| 354 | 376 | |
| 355 | 377 | /** |
| 356 | 378 | Initializes the checkin/file selector widget. Must only be |
| 357 | 379 | called once. |
| 358 | 380 | */ |
| 359 | 381 | init: function(){ |
| 382 | + this.cache.branchNames = F.storage.getJSON(this.cache.branchKey, {}); | |
| 360 | 383 | const selCi = this.e.selectCi = D.select(), |
| 361 | 384 | selFiles = this.e.selectFiles |
| 362 | 385 | = D.addClass(D.select(), 'file-list'), |
| 363 | 386 | btnLoad = this.e.btnLoadFile = |
| 364 | 387 | D.addClass(D.button("Load file"), "flex-shrink"), |
| @@ -488,17 +511,20 @@ | ||
| 488 | 511 | D.removeClass(this.e.btnClear, 'hidden'); |
| 489 | 512 | D.disable(D.option(this.e.select,0,"Select a local edit...")); |
| 490 | 513 | const currentFinfo = theFinfo || P.finfo || {}; |
| 491 | 514 | ilist.sort(f.compare).forEach(function(finfo,n){ |
| 492 | 515 | const key = stasher.indexKey(finfo), |
| 493 | - branch = P.fileSelectWidget.checkinBranchName(finfo.checkin); | |
| 516 | + branch = finfo.branch | |
| 517 | + || P.fileSelectWidget.checkinBranchName(finfo.checkin)||''; | |
| 518 | + /* Remember that we don't know the branch name for non-leaf versions | |
| 519 | + which P.fileSelectWidget() has never seen/cached. */ | |
| 494 | 520 | const opt = D.option( |
| 495 | 521 | self.e.select, n+1/*value is (almost) irrelevant*/, |
| 496 | - [F.hashDigits(finfo.checkin, 6), branch, | |
| 497 | - f.timestring(new Date(finfo.stashTime)), | |
| 522 | + [F.hashDigits(finfo.checkin, 6), ' [',branch||'?branch?','] ', | |
| 523 | + f.timestring(new Date(finfo.stashTime)),' ', | |
| 498 | 524 | false ? finfo.filename : F.shortenFilename(finfo.filename) |
| 499 | - ].join(' ') | |
| 525 | + ].join('') | |
| 500 | 526 | ); |
| 501 | 527 | opt._finfo = finfo; |
| 502 | 528 | if(0===f.compare(currentFinfo, finfo)){ |
| 503 | 529 | D.attr(opt, 'selected', true); |
| 504 | 530 | } |
| @@ -1139,10 +1165,11 @@ | ||
| 1139 | 1165 | */ |
| 1140 | 1166 | P.stashContentChange = function(onlyFinfo){ |
| 1141 | 1167 | if(affirmHasFile(true)){ |
| 1142 | 1168 | const fi = this.finfo; |
| 1143 | 1169 | fi.isExe = this.e.cbIsExe.checked; |
| 1170 | + if(!fi.branch) fi.branch = this.fileSelectWidget.checkinBranchName(fi.checkin); | |
| 1144 | 1171 | if(onlyFinfo && $stash.hasStashedContent(fi)){ |
| 1145 | 1172 | $stash.updateFile(fi); |
| 1146 | 1173 | }else{ |
| 1147 | 1174 | $stash.updateFile(fi, P.fileContent()); |
| 1148 | 1175 | } |
| 1149 | 1176 |
| --- src/fossil.page.fileedit.js | |
| +++ src/fossil.page.fileedit.js | |
| @@ -112,11 +112,11 @@ | |
| 112 | and that would be horribly inefficient (meaning "battery-consuming" |
| 113 | on mobile devices). |
| 114 | */ |
| 115 | const $stash = { |
| 116 | keys: { |
| 117 | index: F.page.name+':index' |
| 118 | }, |
| 119 | /** |
| 120 | index: { |
| 121 | "CHECKIN_HASH:FILENAME": {file info w/o content} |
| 122 | ... |
| @@ -138,11 +138,30 @@ | |
| 138 | by prepending P.name to suffix. */ |
| 139 | contentKey: function(suffix){return P.name+'/'+suffix}, |
| 140 | /** Returns the index object, fetching it from the stash or creating |
| 141 | it anew on the first call. */ |
| 142 | getIndex: function(){ |
| 143 | if(!this.index) this.index = F.storage.getJSON(this.keys.index,{}); |
| 144 | return this.index; |
| 145 | }, |
| 146 | _fireStashEvent: function(){ |
| 147 | if(this._disableNextEvent) delete this._disableNextEvent; |
| 148 | else F.page.dispatchEvent('fileedit-stash-updated', this); |
| @@ -173,10 +192,11 @@ | |
| 173 | filename: finfo.filename, |
| 174 | mimetype: finfo.mimetype |
| 175 | }); |
| 176 | record.isExe = !!finfo.isExe; |
| 177 | record.stashTime = new Date().getTime(); |
| 178 | this.storeIndex(); |
| 179 | if(arguments.length>1){ |
| 180 | F.storage.set(this.contentKey(key), content); |
| 181 | } |
| 182 | this._fireStashEvent(); |
| @@ -256,10 +276,11 @@ | |
| 256 | }, |
| 257 | finfo: {}, |
| 258 | cache: { |
| 259 | checkins: undefined, |
| 260 | files:{}, |
| 261 | branchNames: {} |
| 262 | }, |
| 263 | /** |
| 264 | Fetches the list of leaf checkins from the server and updates |
| 265 | the UI with that list. |
| @@ -281,15 +302,16 @@ | |
| 281 | self.cache.checkins = list; |
| 282 | D.clearElement(D.enable(self.e.selectCi)); |
| 283 | let loadThisOne; |
| 284 | list.forEach(function(o,n){ |
| 285 | if(!n) loadThisOne = o; |
| 286 | self.cache.branchNames[F.hashDigits(o.checkin)] = o.branch; |
| 287 | D.option(self.e.selectCi, o.checkin, |
| 288 | o.timestamp+' ['+o.branch+']: ' |
| 289 | +F.hashDigits(o.checkin)); |
| 290 | }); |
| 291 | self.loadFiles(loadThisOne ? loadThisOne.checkin : false); |
| 292 | } |
| 293 | }); |
| 294 | }, |
| 295 | /** |
| @@ -347,18 +369,19 @@ | |
| 347 | If this object has ever loaded the given checkin version via |
| 348 | loadLeaves(), this returns the branch name associated with that |
| 349 | version, else returns undefined; |
| 350 | */ |
| 351 | checkinBranchName: function(uuid){ |
| 352 | return this.cache.branchNames[F.hashDigits(uuid)]; |
| 353 | }, |
| 354 | |
| 355 | /** |
| 356 | Initializes the checkin/file selector widget. Must only be |
| 357 | called once. |
| 358 | */ |
| 359 | init: function(){ |
| 360 | const selCi = this.e.selectCi = D.select(), |
| 361 | selFiles = this.e.selectFiles |
| 362 | = D.addClass(D.select(), 'file-list'), |
| 363 | btnLoad = this.e.btnLoadFile = |
| 364 | D.addClass(D.button("Load file"), "flex-shrink"), |
| @@ -488,17 +511,20 @@ | |
| 488 | D.removeClass(this.e.btnClear, 'hidden'); |
| 489 | D.disable(D.option(this.e.select,0,"Select a local edit...")); |
| 490 | const currentFinfo = theFinfo || P.finfo || {}; |
| 491 | ilist.sort(f.compare).forEach(function(finfo,n){ |
| 492 | const key = stasher.indexKey(finfo), |
| 493 | branch = P.fileSelectWidget.checkinBranchName(finfo.checkin); |
| 494 | const opt = D.option( |
| 495 | self.e.select, n+1/*value is (almost) irrelevant*/, |
| 496 | [F.hashDigits(finfo.checkin, 6), branch, |
| 497 | f.timestring(new Date(finfo.stashTime)), |
| 498 | false ? finfo.filename : F.shortenFilename(finfo.filename) |
| 499 | ].join(' ') |
| 500 | ); |
| 501 | opt._finfo = finfo; |
| 502 | if(0===f.compare(currentFinfo, finfo)){ |
| 503 | D.attr(opt, 'selected', true); |
| 504 | } |
| @@ -1139,10 +1165,11 @@ | |
| 1139 | */ |
| 1140 | P.stashContentChange = function(onlyFinfo){ |
| 1141 | if(affirmHasFile(true)){ |
| 1142 | const fi = this.finfo; |
| 1143 | fi.isExe = this.e.cbIsExe.checked; |
| 1144 | if(onlyFinfo && $stash.hasStashedContent(fi)){ |
| 1145 | $stash.updateFile(fi); |
| 1146 | }else{ |
| 1147 | $stash.updateFile(fi, P.fileContent()); |
| 1148 | } |
| 1149 |
| --- src/fossil.page.fileedit.js | |
| +++ src/fossil.page.fileedit.js | |
| @@ -112,11 +112,11 @@ | |
| 112 | and that would be horribly inefficient (meaning "battery-consuming" |
| 113 | on mobile devices). |
| 114 | */ |
| 115 | const $stash = { |
| 116 | keys: { |
| 117 | index: F.page.name+'/index' |
| 118 | }, |
| 119 | /** |
| 120 | index: { |
| 121 | "CHECKIN_HASH:FILENAME": {file info w/o content} |
| 122 | ... |
| @@ -138,11 +138,30 @@ | |
| 138 | by prepending P.name to suffix. */ |
| 139 | contentKey: function(suffix){return P.name+'/'+suffix}, |
| 140 | /** Returns the index object, fetching it from the stash or creating |
| 141 | it anew on the first call. */ |
| 142 | getIndex: function(){ |
| 143 | if(!this.index){ |
| 144 | this.index = F.storage.getJSON( |
| 145 | this.keys.index, undefined |
| 146 | ); |
| 147 | if(!this.index){ |
| 148 | /*check for and remove/replace older name. This whole block |
| 149 | can be removed once the test phase is done (don't want to |
| 150 | invalidate the testers' edits on the test server). When |
| 151 | doing so, be sure to replace undefined in the above |
| 152 | getJSON() call with {}. */ |
| 153 | const oldName = F.page.name+':index'; |
| 154 | this.index = F.storage.getJSON(oldName,undefined); |
| 155 | if(this.index){ |
| 156 | F.storage.remove(oldName); |
| 157 | this.storeIndex(); |
| 158 | }else{ |
| 159 | this.index = {}; |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | return this.index; |
| 164 | }, |
| 165 | _fireStashEvent: function(){ |
| 166 | if(this._disableNextEvent) delete this._disableNextEvent; |
| 167 | else F.page.dispatchEvent('fileedit-stash-updated', this); |
| @@ -173,10 +192,11 @@ | |
| 192 | filename: finfo.filename, |
| 193 | mimetype: finfo.mimetype |
| 194 | }); |
| 195 | record.isExe = !!finfo.isExe; |
| 196 | record.stashTime = new Date().getTime(); |
| 197 | if(!record.branch) record.branch=finfo.branch; |
| 198 | this.storeIndex(); |
| 199 | if(arguments.length>1){ |
| 200 | F.storage.set(this.contentKey(key), content); |
| 201 | } |
| 202 | this._fireStashEvent(); |
| @@ -256,10 +276,11 @@ | |
| 276 | }, |
| 277 | finfo: {}, |
| 278 | cache: { |
| 279 | checkins: undefined, |
| 280 | files:{}, |
| 281 | branchKey: 'fileedit/uuid-branches', |
| 282 | branchNames: {} |
| 283 | }, |
| 284 | /** |
| 285 | Fetches the list of leaf checkins from the server and updates |
| 286 | the UI with that list. |
| @@ -281,15 +302,16 @@ | |
| 302 | self.cache.checkins = list; |
| 303 | D.clearElement(D.enable(self.e.selectCi)); |
| 304 | let loadThisOne; |
| 305 | list.forEach(function(o,n){ |
| 306 | if(!n) loadThisOne = o; |
| 307 | self.cache.branchNames[F.hashDigits(o.checkin,true)] = o.branch; |
| 308 | D.option(self.e.selectCi, o.checkin, |
| 309 | o.timestamp+' ['+o.branch+']: ' |
| 310 | +F.hashDigits(o.checkin)); |
| 311 | }); |
| 312 | F.storage.setJSON(self.cache.branchKey, self.cache.branchNames); |
| 313 | self.loadFiles(loadThisOne ? loadThisOne.checkin : false); |
| 314 | } |
| 315 | }); |
| 316 | }, |
| 317 | /** |
| @@ -347,18 +369,19 @@ | |
| 369 | If this object has ever loaded the given checkin version via |
| 370 | loadLeaves(), this returns the branch name associated with that |
| 371 | version, else returns undefined; |
| 372 | */ |
| 373 | checkinBranchName: function(uuid){ |
| 374 | return this.cache.branchNames[F.hashDigits(uuid,true)]; |
| 375 | }, |
| 376 | |
| 377 | /** |
| 378 | Initializes the checkin/file selector widget. Must only be |
| 379 | called once. |
| 380 | */ |
| 381 | init: function(){ |
| 382 | this.cache.branchNames = F.storage.getJSON(this.cache.branchKey, {}); |
| 383 | const selCi = this.e.selectCi = D.select(), |
| 384 | selFiles = this.e.selectFiles |
| 385 | = D.addClass(D.select(), 'file-list'), |
| 386 | btnLoad = this.e.btnLoadFile = |
| 387 | D.addClass(D.button("Load file"), "flex-shrink"), |
| @@ -488,17 +511,20 @@ | |
| 511 | D.removeClass(this.e.btnClear, 'hidden'); |
| 512 | D.disable(D.option(this.e.select,0,"Select a local edit...")); |
| 513 | const currentFinfo = theFinfo || P.finfo || {}; |
| 514 | ilist.sort(f.compare).forEach(function(finfo,n){ |
| 515 | const key = stasher.indexKey(finfo), |
| 516 | branch = finfo.branch |
| 517 | || P.fileSelectWidget.checkinBranchName(finfo.checkin)||''; |
| 518 | /* Remember that we don't know the branch name for non-leaf versions |
| 519 | which P.fileSelectWidget() has never seen/cached. */ |
| 520 | const opt = D.option( |
| 521 | self.e.select, n+1/*value is (almost) irrelevant*/, |
| 522 | [F.hashDigits(finfo.checkin, 6), ' [',branch||'?branch?','] ', |
| 523 | f.timestring(new Date(finfo.stashTime)),' ', |
| 524 | false ? finfo.filename : F.shortenFilename(finfo.filename) |
| 525 | ].join('') |
| 526 | ); |
| 527 | opt._finfo = finfo; |
| 528 | if(0===f.compare(currentFinfo, finfo)){ |
| 529 | D.attr(opt, 'selected', true); |
| 530 | } |
| @@ -1139,10 +1165,11 @@ | |
| 1165 | */ |
| 1166 | P.stashContentChange = function(onlyFinfo){ |
| 1167 | if(affirmHasFile(true)){ |
| 1168 | const fi = this.finfo; |
| 1169 | fi.isExe = this.e.cbIsExe.checked; |
| 1170 | if(!fi.branch) fi.branch = this.fileSelectWidget.checkinBranchName(fi.checkin); |
| 1171 | if(onlyFinfo && $stash.hasStashedContent(fi)){ |
| 1172 | $stash.updateFile(fi); |
| 1173 | }else{ |
| 1174 | $stash.updateFile(fi, P.fileContent()); |
| 1175 | } |
| 1176 |
+10
-4
| --- src/fossil.storage.js | ||
| +++ src/fossil.storage.js | ||
| @@ -66,14 +66,20 @@ | ||
| 66 | 66 | catch(e){return dflt} |
| 67 | 67 | }, |
| 68 | 68 | /** Returns true if the storage contains the given key, |
| 69 | 69 | else false. */ |
| 70 | 70 | contains: (k)=>$storageHolder.hasOwnProperty(k), |
| 71 | - /** Removes the given key from the storage. */ | |
| 72 | - remove: (k)=>$storage.removeItem(k), | |
| 73 | - /** Clears ALL keys from the storage. */ | |
| 74 | - clear: ()=>$storage.clear(), | |
| 71 | + /** Removes the given key from the storage. Returns this. */ | |
| 72 | + remove: function(k){ | |
| 73 | + $storage.removeItem(k); | |
| 74 | + return this; | |
| 75 | + }, | |
| 76 | + /** Clears ALL keys from the storage. Returns this. */ | |
| 77 | + clear: function(){ | |
| 78 | + $storage.clear(); | |
| 79 | + return this; | |
| 80 | + }, | |
| 75 | 81 | /** Returns an array of all keys currently in the storage. */ |
| 76 | 82 | keys: ()=>Object.keys($storageHolder), |
| 77 | 83 | /** Returns true if this storage is transient (only available |
| 78 | 84 | until the page is reloaded), indicating that fileStorage |
| 79 | 85 | and sessionStorage are unavailable. */ |
| 80 | 86 |
| --- src/fossil.storage.js | |
| +++ src/fossil.storage.js | |
| @@ -66,14 +66,20 @@ | |
| 66 | catch(e){return dflt} |
| 67 | }, |
| 68 | /** Returns true if the storage contains the given key, |
| 69 | else false. */ |
| 70 | contains: (k)=>$storageHolder.hasOwnProperty(k), |
| 71 | /** Removes the given key from the storage. */ |
| 72 | remove: (k)=>$storage.removeItem(k), |
| 73 | /** Clears ALL keys from the storage. */ |
| 74 | clear: ()=>$storage.clear(), |
| 75 | /** Returns an array of all keys currently in the storage. */ |
| 76 | keys: ()=>Object.keys($storageHolder), |
| 77 | /** Returns true if this storage is transient (only available |
| 78 | until the page is reloaded), indicating that fileStorage |
| 79 | and sessionStorage are unavailable. */ |
| 80 |
| --- src/fossil.storage.js | |
| +++ src/fossil.storage.js | |
| @@ -66,14 +66,20 @@ | |
| 66 | catch(e){return dflt} |
| 67 | }, |
| 68 | /** Returns true if the storage contains the given key, |
| 69 | else false. */ |
| 70 | contains: (k)=>$storageHolder.hasOwnProperty(k), |
| 71 | /** Removes the given key from the storage. Returns this. */ |
| 72 | remove: function(k){ |
| 73 | $storage.removeItem(k); |
| 74 | return this; |
| 75 | }, |
| 76 | /** Clears ALL keys from the storage. Returns this. */ |
| 77 | clear: function(){ |
| 78 | $storage.clear(); |
| 79 | return this; |
| 80 | }, |
| 81 | /** Returns an array of all keys currently in the storage. */ |
| 82 | keys: ()=>Object.keys($storageHolder), |
| 83 | /** Returns true if this storage is transient (only available |
| 84 | until the page is reloaded), indicating that fileStorage |
| 85 | and sessionStorage are unavailable. */ |
| 86 |