Skip to content

Commit e95a9e5

Browse files
authored
Fix stash REST API (#1280)
* WIP parse stash on the backend * Parse stash on the backend * Remove duplicate workflow * Update frontend to new REST API * Fix typing for stash index * Lint the code * Fix index parsed on the backend * Improve test robustness
1 parent a8cff2b commit e95a9e5

File tree

9 files changed

+109
-155
lines changed

9 files changed

+109
-155
lines changed

.github/workflows/pre-release.yml

Lines changed: 0 additions & 41 deletions
This file was deleted.

jupyterlab_git/git.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
GIT_REBASING_BRANCH = re.compile(r"^\(no branch, rebasing (?P<branch>.+?)\)$")
4747
# Git cache as a credential helper
4848
GIT_CREDENTIAL_HELPER_CACHE = re.compile(r"cache\b")
49+
# Parse git stash list
50+
GIT_STASH_LIST = re.compile(
51+
r"^stash@{(?P<index>\d+)}: (WIP on|On) (?P<branch>.+?): (?P<message>.+?)$"
52+
)
4953

5054
execution_lock = tornado.locks.Lock()
5155

@@ -1985,7 +1989,8 @@ async def stash(self, path: str, stashMsg: str = "") -> dict:
19851989
# code 0: no changes to stash
19861990
if code != 0:
19871991
return {"code": code, "command": " ".join(cmd), "message": error}
1988-
return {"code": code, "message": output, "command": " ".join(cmd)}
1992+
1993+
return {"code": code, "message": output.strip()}
19891994

19901995
async def stash_list(self, path: str) -> dict:
19911996
"""
@@ -2001,7 +2006,15 @@ async def stash_list(self, path: str) -> dict:
20012006
if code != 0:
20022007
return {"code": code, "command": " ".join(cmd), "message": error}
20032008

2004-
return {"code": code, "message": output, "command": " ".join(cmd)}
2009+
stashes = []
2010+
for line in output.strip("\n").splitlines():
2011+
match = GIT_STASH_LIST.match(line)
2012+
if match is not None:
2013+
d = match.groupdict()
2014+
d["index"] = int(d["index"])
2015+
stashes.append(d)
2016+
2017+
return {"code": code, "stashes": stashes}
20052018

20062019
async def stash_show(self, path: str, index: int) -> dict:
20072020
"""
@@ -2020,7 +2033,9 @@ async def stash_show(self, path: str, index: int) -> dict:
20202033
if code != 0:
20212034
return {"code": code, "command": " ".join(cmd), "message": error}
20222035

2023-
return {"code": code, "message": output, "command": " ".join(cmd)}
2036+
files = output.strip("\n").splitlines()
2037+
2038+
return {"code": code, "files": files}
20242039

20252040
async def pop_stash(self, path: str, stash_index: Optional[int] = None) -> dict:
20262041
"""
@@ -2045,7 +2060,7 @@ async def pop_stash(self, path: str, stash_index: Optional[int] = None) -> dict:
20452060
if code != 0:
20462061
return {"code": code, "command": " ".join(cmd), "message": error}
20472062

2048-
return {"code": code, "message": output, "command": " ".join(cmd)}
2063+
return {"code": code, "message": output.strip()}
20492064

20502065
async def drop_stash(self, path, stash_index: Optional[int] = None) -> dict:
20512066
"""
@@ -2071,7 +2086,7 @@ async def drop_stash(self, path, stash_index: Optional[int] = None) -> dict:
20712086
if code != 0:
20722087
return {"code": code, "command": " ".join(cmd), "message": error}
20732088

2074-
return {"code": code, "message": output, "command": " ".join(cmd)}
2089+
return {"code": code, "message": output.strip()}
20752090

20762091
async def apply_stash(self, path: str, stash_index: Optional[int] = None) -> dict:
20772092
"""
@@ -2098,7 +2113,7 @@ async def apply_stash(self, path: str, stash_index: Optional[int] = None) -> dic
20982113
if code != 0:
20992114
return {"code": code, "command": " ".join(cmd), "message": error}
21002115

2101-
return {"code": code, "message": output, "command": " ".join(cmd)}
2116+
return {"code": code, "message": output.strip()}
21022117

21032118
@property
21042119
def excluded_paths(self) -> List[str]:

jupyterlab_git/handlers.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -964,8 +964,7 @@ async def post(self, path: str = "", stashMsg: str = ""):
964964
self.set_status(201)
965965
else:
966966
self.set_status(500)
967-
968-
self.finish(json.dumps(response))
967+
self.finish(json.dumps(response))
969968

970969
@tornado.web.authenticated
971970
async def delete(self, path: str = ""):
@@ -983,9 +982,10 @@ async def delete(self, path: str = ""):
983982

984983
if response["code"] == 0:
985984
self.set_status(204)
985+
self.finish()
986986
else:
987987
self.set_status(500)
988-
self.finish()
988+
self.finish(json.dumps(response))
989989

990990
@tornado.web.authenticated
991991
async def get(self, path: str = ""):
@@ -1048,8 +1048,7 @@ async def post(self, path: str = ""):
10481048
self.set_status(201)
10491049
else:
10501050
self.set_status(500)
1051-
1052-
self.finish(json.dumps(response))
1051+
self.finish(json.dumps(response))
10531052

10541053

10551054
def setup_handlers(web_app):

jupyterlab_git/tests/test_stash.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,6 @@ async def test_git_stash_without_message(mock_execute, jp_fetch, jp_root_dir):
4242
)
4343

4444
assert response.code == 201
45-
# json.loads turns a string into a dictionary
46-
payload = json.loads(response.body)
47-
assert payload == {
48-
"code": 0,
49-
"message": "",
50-
"command": " ".join(command),
51-
}
5245

5346

5447
@patch("jupyterlab_git.git.execute")
@@ -115,12 +108,6 @@ async def test_git_stash_with_message(mock_execute, jp_fetch, jp_root_dir):
115108
)
116109

117110
assert response.code == 201
118-
payload = json.loads(response.body)
119-
assert payload == {
120-
"code": 0,
121-
"message": "",
122-
"command": " ".join(command),
123-
}
124111

125112

126113
# Git Stash - GET
@@ -134,7 +121,13 @@ async def test_git_stash_show_with_index(mock_execute, jp_fetch, jp_root_dir):
134121
local_path = jp_root_dir / "test_path"
135122
stash_index = 1
136123

137-
mock_execute.return_value = maybe_future((0, "", ""))
124+
mock_execute.return_value = maybe_future(
125+
(
126+
0,
127+
"node_modules/playwright-core/README.md\nnode_modules/playwright-core/package.json\n",
128+
"",
129+
)
130+
)
138131

139132
# When
140133
response = await jp_fetch(
@@ -164,8 +157,10 @@ async def test_git_stash_show_with_index(mock_execute, jp_fetch, jp_root_dir):
164157
payload = json.loads(response.body)
165158
assert payload == {
166159
"code": 0,
167-
"message": "",
168-
"command": " ".join(command),
160+
"files": [
161+
"node_modules/playwright-core/README.md",
162+
"node_modules/playwright-core/package.json",
163+
],
169164
}
170165

171166

@@ -176,7 +171,13 @@ async def test_git_stash_show_without_index(mock_execute, jp_fetch, jp_root_dir)
176171
env["GIT_TERMINAL_PROMPT"] = "0"
177172
local_path = jp_root_dir / "test_path"
178173

179-
mock_execute.return_value = maybe_future((0, "", ""))
174+
mock_execute.return_value = maybe_future(
175+
(
176+
0,
177+
"stash@{0}: On main: testsd\nstash@{1}: WIP on main: bea6895 first commit\n",
178+
"",
179+
)
180+
)
180181

181182
# When
182183
response = await jp_fetch(
@@ -202,8 +203,10 @@ async def test_git_stash_show_without_index(mock_execute, jp_fetch, jp_root_dir)
202203
payload = json.loads(response.body)
203204
assert payload == {
204205
"code": 0,
205-
"message": "",
206-
"command": " ".join(command),
206+
"stashes": [
207+
{"index": 0, "branch": "main", "message": "testsd"},
208+
{"index": 1, "branch": "main", "message": "bea6895 first commit"},
209+
],
207210
}
208211

209212

src/components/GitPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export interface IGitPanelState {
154154
* Stashed files
155155
*
156156
*/
157-
stash: Git.IStash;
157+
stash: Git.IStash[];
158158
}
159159

160160
/**

src/components/GitStash.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ export interface IGitStashProps {
4747
/**
4848
* Files in the group
4949
*/
50-
stash: Git.IStash;
50+
stash: Git.IStash[];
51+
5152
/**
5253
* HTML element height
5354
*/

src/model.ts

Lines changed: 27 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -266,14 +266,14 @@ export class GitExtension implements IGitExtension {
266266
* A signal emitted when the Git stash changes.
267267
*
268268
*/
269-
get stashChanged(): ISignal<IGitExtension, IChangedArgs<Git.IStash>> {
269+
get stashChanged(): ISignal<IGitExtension, IChangedArgs<Git.IStash[]>> {
270270
return this._stashChanged;
271271
}
272272

273273
/**
274274
* The repository stash
275275
*/
276-
get stash(): Git.IStash {
276+
get stash(): Git.IStash[] {
277277
return this._stash;
278278
}
279279

@@ -1655,64 +1655,36 @@ export class GitExtension implements IGitExtension {
16551655

16561656
// Get the entire stash list
16571657
try {
1658-
const stashListData =
1659-
await this._taskHandler.execute<Git.IStashListResult>(
1660-
'git:refresh:stash',
1661-
async () => {
1662-
return await requestAPI<Git.IStashListResult>(
1663-
URLExt.join(path, 'stash'),
1664-
'GET'
1665-
);
1666-
}
1667-
);
1668-
1669-
// Contains the raw message
1670-
const stashMsgList = stashListData.message.split('\n').slice(0, -1);
1671-
1672-
const stashList: Git.IStashEntry[] = [];
1673-
for (const index in stashMsgList) {
1674-
const stashInfo = stashMsgList[index].split(':');
1675-
1676-
const WIPMatch = stashInfo[1].match(/WIP on (.*)/);
1677-
const branchName = WIPMatch ? WIPMatch[1] : stashInfo[1].split(' ')[2];
1678-
1679-
const stashMsg = stashInfo[2].replace(/^\s+/, '');
1680-
1681-
stashList.push({
1682-
index: parseInt(index, 10),
1683-
branch: branchName,
1684-
message: stashMsg,
1685-
files: []
1686-
});
1687-
}
1688-
const fileData = await this._taskHandler.execute<Git.IStashListResult>(
1658+
const response = await this._taskHandler.execute<Git.IStashListResult>(
16891659
'git:refresh:stash',
16901660
async () => {
1691-
const results = await Promise.all(
1692-
stashList.map(({ index }) =>
1693-
requestAPI<Git.IStashListResult>(
1694-
URLExt.join(path, 'stash') + `?index=${index}`,
1695-
'GET'
1696-
)
1697-
)
1661+
return await requestAPI<Git.IStashListResult>(
1662+
URLExt.join(path, 'stash'),
1663+
'GET'
16981664
);
1699-
return {
1700-
message: 'Stash list result',
1701-
command: 'git:refresh:stash',
1702-
code: 0,
1703-
results
1704-
};
17051665
}
17061666
);
17071667

1708-
stashList.forEach((stash, index) => {
1709-
stash.files.push(
1710-
...(fileData.results ?? [])[index].message.split('\n').slice(0, -1)
1711-
);
1712-
});
1668+
const allStashFiles = await this._taskHandler.execute<
1669+
Git.IStashShowResult[]
1670+
>('git:refresh:stash', () =>
1671+
Promise.all(
1672+
response.stashes.map(({ index }) =>
1673+
requestAPI<Git.IStashShowResult>(
1674+
URLExt.join(path, 'stash') + `?index=${index}`,
1675+
'GET'
1676+
)
1677+
)
1678+
)
1679+
);
1680+
const stashList: Git.IStash[] = response.stashes.map((s, index) =>
1681+
Object.assign(s, {
1682+
files: allStashFiles[index].files
1683+
})
1684+
);
17131685

17141686
if (!this.isStashDeepEqual(stashList, this._stash)) {
1715-
const change: IChangedArgs<Git.IStash> = {
1687+
const change: IChangedArgs<Git.IStash[]> = {
17161688
name: 'stash',
17171689
newValue: stashList,
17181690
oldValue: this._stash
@@ -1772,10 +1744,7 @@ export class GitExtension implements IGitExtension {
17721744
* @throws {Git.GitResponseError} If the server response is not ok
17731745
* @throws {ServerConnection.NetworkError} If the request cannot be made
17741746
*/
1775-
protected isStashDeepEqual(
1776-
a: Git.IStashEntry[],
1777-
b: Git.IStashEntry[]
1778-
): boolean {
1747+
protected isStashDeepEqual(a: Git.IStash[], b: Git.IStash[]): boolean {
17791748
if (a?.length !== b?.length) {
17801749
return false;
17811750
}
@@ -2212,7 +2181,7 @@ export class GitExtension implements IGitExtension {
22122181
state: Git.State.DEFAULT,
22132182
files: []
22142183
};
2215-
private _stash: Git.IStash = [];
2184+
private _stash: Git.IStash[] = [];
22162185
private _pathRepository: string | null = null;
22172186
private _branches: Git.IBranch[] = [];
22182187
private _tagsList: Git.ITag[] = [];
@@ -2251,7 +2220,7 @@ export class GitExtension implements IGitExtension {
22512220
IGitExtension,
22522221
IChangedArgs<string | null>
22532222
>(this);
2254-
private _stashChanged = new Signal<IGitExtension, IChangedArgs<Git.IStash>>(
2223+
private _stashChanged = new Signal<IGitExtension, IChangedArgs<Git.IStash[]>>(
22552224
this
22562225
);
22572226
private _statusChanged = new Signal<IGitExtension, Git.IStatus>(this);

0 commit comments

Comments
 (0)