megit-app 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -1
- package/README.md +3 -1
- package/bin/megit.js +19 -8
- package/dist/assets/{ConflictView-CHWx-7wL.js → ConflictView-C2Luxy2W.js} +1 -1
- package/dist/assets/DiffView-CTUpzVA_.js +143 -0
- package/dist/assets/{TerminalPanel-DI0qzKCw.js → TerminalPanel-i6ekLzMJ.js} +9 -11
- package/dist/assets/index-BlIDqqEW.js +251 -0
- package/dist/assets/{index-C5YS6cns.css → index-ByiPZ5H3.css} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/index.js +29 -7
- package/package.json +6 -6
- package/dist/assets/DiffView-DXXDhOBr.js +0 -143
- package/dist/assets/index-Ch9W5KCh.js +0 -251
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,30 @@ surface, and the HTTP API may change in any minor release.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.9.0] - 2026-08-28
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Returning to the megit browser tab runs a full refresh — spinner and remote fetch, at most
|
|
17
|
+
once every 10 seconds. A background tab can have its event stream throttled or dropped, so
|
|
18
|
+
waiting for a change event was not enough.
|
|
19
|
+
- `megit start` now restarts a server that is already running instead of reporting
|
|
20
|
+
"already running", so an upgraded version actually takes effect. It also replaces a
|
|
21
|
+
server running on another port rather than refusing to start.
|
|
22
|
+
|
|
23
|
+
## [0.8.0] - 2026-08-20
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- An **Amend last commit** checkbox in the commit composer folds staged changes into the
|
|
28
|
+
tip and prefills its message. Refuses mid-merge or on a detached HEAD, and asks again
|
|
29
|
+
before rewriting a commit a remote already has.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Toolbar actions are disabled while an operation is in flight, so a second click can't
|
|
34
|
+
fire a git command on top of the running one.
|
|
35
|
+
|
|
12
36
|
## [0.7.0] - 2026-08-19
|
|
13
37
|
|
|
14
38
|
### Added
|
|
@@ -212,7 +236,9 @@ First public release.
|
|
|
212
236
|
- Windows (arm64, x64): builds and runs, but untested on real hardware — recursive `fs.watch` crashes the test worker there, so auto-refresh is not covered by CI
|
|
213
237
|
- Linux: everything except the terminal — node-pty is an optionalDependency with no Linux prebuild, and the terminal button is hidden when it is unavailable
|
|
214
238
|
|
|
215
|
-
[Unreleased]: https://github.com/vuongvu1/megit/compare/v0.
|
|
239
|
+
[Unreleased]: https://github.com/vuongvu1/megit/compare/v0.9.0...HEAD
|
|
240
|
+
[0.9.0]: https://github.com/vuongvu1/megit/compare/v0.8.0...v0.9.0
|
|
241
|
+
[0.8.0]: https://github.com/vuongvu1/megit/compare/v0.7.0...v0.8.0
|
|
216
242
|
[0.7.0]: https://github.com/vuongvu1/megit/compare/v0.6.0...v0.7.0
|
|
217
243
|
[0.6.0]: https://github.com/vuongvu1/megit/compare/v0.5.2...v0.6.0
|
|
218
244
|
[0.5.2]: https://github.com/vuongvu1/megit/compare/v0.5.1...v0.5.2
|
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ npx megit-app start
|
|
|
25
25
|
npx megit-app stop
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
`start` restarts a server that is already running, so a fresh version takes effect.
|
|
29
|
+
|
|
28
30
|
Or install it once with `npm i -g megit-app` and run `megit`, `megit start` and `megit stop` from anywhere; upgrade later with `npm i -g megit-app@latest`.
|
|
29
31
|
|
|
30
32
|
<sub>The package is `megit-app`; `megit` on npm is unrelated, so keep the `-app`. The installed command is still `megit`.</sub>
|
|
@@ -92,7 +94,7 @@ megit started as a viewer, but the common operations are here:
|
|
|
92
94
|
- **Toolbar** — pull (fast-forward only), push, create branch, stash all, pop latest stash, undo last commit (soft reset, keeps changes staged)
|
|
93
95
|
- **Ref chips** — checkout, create branch here, rename, delete, set upstream, merge, rebase, delete tag, copy name, copy GitHub link
|
|
94
96
|
- **Commit rows** — checkout, cherry-pick, revert, reset (soft / mixed / hard), copy hash, copy GitHub link
|
|
95
|
-
- **Files** — stage, unstage, discard, amend the last commit
|
|
97
|
+
- **Files** — stage, unstage, discard, commit, amend the last commit (message, staged changes, or both)
|
|
96
98
|
|
|
97
99
|
Checkout auto-stashes a dirty worktree first. Destructive items are marked as such and are hidden where they'd be meaningless.
|
|
98
100
|
|
package/bin/megit.js
CHANGED
|
@@ -16,7 +16,7 @@ if (cmd === '-h' || cmd === '--help') {
|
|
|
16
16
|
|
|
17
17
|
Usage:
|
|
18
18
|
megit run in this terminal
|
|
19
|
-
megit start run in the background
|
|
19
|
+
megit start run in the background (restarts it if already running)
|
|
20
20
|
megit stop stop the background server
|
|
21
21
|
|
|
22
22
|
Options:
|
|
@@ -68,17 +68,28 @@ const megitAnswers = async p => {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// A dead process frees its port immediately, but `process.kill` returns before
|
|
72
|
+
// the exit has happened — spawning the replacement first would race the bind.
|
|
73
|
+
const waitForExit = async pid => {
|
|
74
|
+
for (let i = 0; i < 50 && pidAlive(pid); i++) await new Promise(r => setTimeout(r, 100))
|
|
75
|
+
return !pidAlive(pid)
|
|
76
|
+
}
|
|
77
|
+
|
|
71
78
|
if (cmd === 'start') {
|
|
72
79
|
const prev = readState()
|
|
80
|
+
// `start` restarts rather than no-ops: after `npm i -g megit-app@latest` the old
|
|
81
|
+
// process is still the old version, and reporting "already running" would leave
|
|
82
|
+
// the upgrade silently unapplied. ponytail: one daemon is tracked, so a running
|
|
83
|
+
// server is replaced whatever port it is on — key the state file by port if
|
|
84
|
+
// anyone actually wants two.
|
|
73
85
|
if (prev && pidAlive(prev.pid) && (await megitAnswers(prev.port))) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (prev.
|
|
77
|
-
console.
|
|
78
|
-
process.exit(
|
|
86
|
+
process.kill(prev.pid, 'SIGTERM')
|
|
87
|
+
rmSync(stateFile, { force: true })
|
|
88
|
+
if (!(await waitForExit(prev.pid))) {
|
|
89
|
+
console.error(`megit: server on http://127.0.0.1:${prev.port} (PID ${prev.pid}) did not stop`)
|
|
90
|
+
process.exit(1)
|
|
79
91
|
}
|
|
80
|
-
console.
|
|
81
|
-
process.exit(1)
|
|
92
|
+
console.log(`megit: restarting (was on http://127.0.0.1:${prev.port})`)
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
mkdirSync(dir, { recursive: true })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,i as t,n,r}from"./index-
|
|
1
|
+
import{a as e,i as t,n,r}from"./index-BlIDqqEW.js";var i=e(),a=/^<<<<<<< ?(.*)$/,o=/^\|\|\|\|\|\|\| ?(.*)$/,s=/^=======$/,c=/^>>>>>>> ?(.*)$/,l=e=>e.replace(/\r?\n$/,``);function u(e){let t=e.split(/(?<=\n)/),n=[],r=[],i=null,u=`context`,d=()=>{r.length&&n.push({kind:`context`,lines:r}),r=[]};for(let e of t){let t=l(e),f=a.exec(t);if(f){if(u!==`context`)return null;d(),i={ours:[],base:null,theirs:[],oursLabel:f[1].trim(),theirsLabel:``},u=`ours`;continue}if(u===`context`){r.push(e);continue}if(o.exec(t)){if(u!==`ours`)return null;i.base=[],u=`base`;continue}if(s.test(t)){if(u!==`ours`&&u!==`base`)return null;u=`theirs`;continue}let p=c.exec(t);if(p){if(u!==`theirs`)return null;i.theirsLabel=p[1].trim(),n.push({kind:`conflict`,block:i}),i=null,u=`context`;continue}u===`ours`?i.ours.push(e):u===`base`?i.base.push(e):i.theirs.push(e)}return u===`context`?(d(),n.some(e=>e.kind===`conflict`)?n:null):null}function d(e,t){let n=[];return e.forEach((e,r)=>{if(e.kind===`context`){n.push(...e.lines);return}let i=t.get(r);if(!i)throw Error(`no pick for conflict at segment ${r}`);(i===`ours`||i===`both`)&&n.push(...e.block.ours),(i===`theirs`||i===`both`)&&n.push(...e.block.theirs)}),n.join(``)}var f=n(),p=[[`ours`,`Use ours`],[`theirs`,`Use theirs`],[`both`,`Use both`]];function m({note:e,busy:t,onPick:n,onMarkResolved:r}){return(0,f.jsxs)(`div`,{className:`cf-card`,children:[(0,f.jsx)(`div`,{className:`cf-card-note`,children:e}),(0,f.jsxs)(`div`,{className:`cf-card-actions`,children:[r&&(0,f.jsx)(`button`,{className:`primary`,disabled:t,onClick:r,children:`Mark resolved`}),(0,f.jsx)(`button`,{disabled:t,onClick:()=>n(`ours`),children:`Keep ours`}),(0,f.jsx)(`button`,{disabled:t,onClick:()=>n(`theirs`),children:`Keep theirs`}),(0,f.jsx)(`button`,{className:`danger`,disabled:t,onClick:()=>n(`delete`),children:`Delete file`})]})]})}function h({repo:e,file:n,onResolved:a}){let[o,s]=(0,i.useState)(null),[c,l]=(0,i.useState)(new Map),[h,g]=(0,i.useState)(``),[_,v]=(0,i.useState)(!1),y=`repo=${encodeURIComponent(e)}`;(0,i.useEffect)(()=>{s(null),l(new Map),g(``),r(`/api/conflict?${y}&file=${encodeURIComponent(n)}`).then(s).catch(e=>g(e.message))},[e,n]);let b=(0,i.useMemo)(()=>o?.content?u(o.content):null,[o]),x=(0,i.useMemo)(()=>b?.filter(e=>e.kind===`conflict`).length??0,[b]);(0,i.useEffect)(()=>{!b||!x||c.size<x||(v(!0),r(`/api/conflict?${y}`,t(`POST`,{action:`resolve`,file:n,content:d(b,c)})).then(a).catch(e=>{g(e.message),v(!1)}))},[c,b,x]);let S=e=>{v(!0),r(`/api/conflict?${y}`,t(`POST`,{action:e,file:n})).then(a).catch(e=>{g(e.message),v(!1)})},C=()=>{v(!0),r(`/api/conflict?${y}`,t(`POST`,{action:`resolve`,file:n,content:o?.content??``})).then(a).catch(e=>{g(e.message),v(!1)})},w=(e,t)=>l(n=>new Map(n).set(e,t)),T=e=>l(t=>{let n=new Map(t);return n.delete(e),n});return h?(0,f.jsx)(`div`,{className:`diffview error`,children:h}):o?o.tooLarge?(0,f.jsxs)(`div`,{className:`diffview empty`,children:[`File too large to resolve here (`,Math.round((o.size??0)/1024),` KB) — use the terminal`]}):o.binary?(0,f.jsx)(`div`,{className:`cf-view`,children:(0,f.jsx)(m,{note:`Binary file — there is nothing to merge line by line.`,busy:_,onPick:S})}):o.missing?(0,f.jsx)(`div`,{className:`cf-view`,children:(0,f.jsx)(m,{note:`Deleted on one side and modified on the other.`,busy:_,onPick:S})}):b?(0,f.jsxs)(`div`,{className:`cf-view`,children:[(0,f.jsxs)(`div`,{className:`cf-progress`,children:[c.size,` of `,x,` resolved`]}),b.map((e,t)=>{if(e.kind===`context`)return(0,f.jsx)(`pre`,{className:`cf-context`,children:e.lines.join(``)},t);let n=c.get(t);return(0,f.jsxs)(`div`,{className:`cf-block${n?` picked`:``}`,children:[(0,f.jsxs)(`div`,{className:`cf-bar`,children:[(0,f.jsx)(`span`,{className:`cf-side-name`,children:e.block.oursLabel||`ours`}),n?(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`span`,{className:`cf-chosen`,children:[`took `,n]}),(0,f.jsx)(`button`,{disabled:_,onClick:()=>T(t),children:`Reset`})]}):p.map(([e,n])=>(0,f.jsx)(`button`,{disabled:_,onClick:()=>w(t,e),children:n},e))]}),(!n||n===`ours`||n===`both`)&&(0,f.jsx)(`pre`,{className:`cf-ours`,children:e.block.ours.join(``)}),!n&&(0,f.jsx)(`div`,{className:`cf-mid`,children:e.block.theirsLabel||`theirs`}),(!n||n===`theirs`||n===`both`)&&(0,f.jsx)(`pre`,{className:`cf-theirs`,children:e.block.theirs.join(``)})]},t)})]}):(0,f.jsx)(`div`,{className:`cf-view`,children:(0,f.jsx)(m,{note:`No conflict markers left in this file — it looks already resolved.`,busy:_,onPick:S,onMarkResolved:C})}):(0,f.jsx)(`div`,{className:`diffview empty`,children:`Loading…`})}export{h as default};
|