Skip to content

Commit 6fd13f4

Browse files
authored
Show tags in history sidebar (#1272)
* show tags in history sidebar * switch to ITag interface * fix tests * fix PastCommitNode.spec test * remove hard coded length of tagsList * change tet_tag.py to ITag interface * fix test_git_tag_success test * fix test_tag * fix tests
1 parent 2b268a7 commit 6fd13f4

File tree

14 files changed

+148
-30
lines changed

14 files changed

+148
-30
lines changed

jupyterlab_git/git.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1736,16 +1736,25 @@ async def version(self):
17361736
return None
17371737

17381738
async def tags(self, path):
1739-
"""List all tags of the git repository.
1739+
"""List all tags of the git repository, including the commit each tag points to.
17401740
17411741
path: str
17421742
Git path repository
17431743
"""
1744-
command = ["git", "tag", "--list"]
1744+
formats = ["refname:short", "objectname"]
1745+
command = [
1746+
"git",
1747+
"for-each-ref",
1748+
"--format=" + "%09".join("%({})".format(f) for f in formats),
1749+
"refs/tags",
1750+
]
17451751
code, output, error = await self.__execute(command, cwd=path)
17461752
if code != 0:
17471753
return {"code": code, "command": " ".join(command), "message": error}
1748-
tags = [tag for tag in output.split("\n") if len(tag) > 0]
1754+
tags = []
1755+
for tag_name, commit_id in (line.split("\t") for line in output.splitlines()):
1756+
tag = {"name": tag_name, "baseCommitId": commit_id}
1757+
tags.append(tag)
17491758
return {"code": code, "tags": tags}
17501759

17511760
async def tag_checkout(self, path, tag):

jupyterlab_git/handlers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,7 @@ async def get(self):
861861

862862
class GitTagHandler(GitHandler):
863863
"""
864-
Handler for 'git tag '. Fetches list of all tags in current repository
864+
Handler for 'git for-each-ref refs/tags'. Fetches list of all tags in current repository
865865
"""
866866

867867
@tornado.web.authenticated

jupyterlab_git/tests/test_tag.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,22 @@
1010
@pytest.mark.asyncio
1111
async def test_git_tag_success():
1212
with patch("jupyterlab_git.git.execute") as mock_execute:
13-
tag = "1.0.0"
13+
output_tags = "v1.0.0\t6db57bf4987d387d439acd16ddfe8d54d46e8f4\nv2.0.1\t2aeae86b6010dd1f05b820d8753cff8349c181a6"
14+
1415
# Given
15-
mock_execute.return_value = maybe_future((0, tag, ""))
16+
mock_execute.return_value = maybe_future((0, output_tags, ""))
1617

1718
# When
1819
actual_response = await Git().tags("test_curr_path")
1920

2021
# Then
2122
mock_execute.assert_called_once_with(
22-
["git", "tag", "--list"],
23+
[
24+
"git",
25+
"for-each-ref",
26+
"--format=%(refname:short)%09%(objectname)",
27+
"refs/tags",
28+
],
2329
cwd="test_curr_path",
2430
timeout=20,
2531
env=None,
@@ -28,7 +34,21 @@ async def test_git_tag_success():
2834
is_binary=False,
2935
)
3036

31-
assert {"code": 0, "tags": [tag]} == actual_response
37+
expected_response = {
38+
"code": 0,
39+
"tags": [
40+
{
41+
"name": "v1.0.0",
42+
"baseCommitId": "6db57bf4987d387d439acd16ddfe8d54d46e8f4",
43+
},
44+
{
45+
"name": "v2.0.1",
46+
"baseCommitId": "2aeae86b6010dd1f05b820d8753cff8349c181a6",
47+
},
48+
],
49+
}
50+
51+
assert expected_response == actual_response
3252

3353

3454
@pytest.mark.asyncio

src/__tests__/test-components/HistorySideBar.spec.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('HistorySideBar', () => {
2727
}
2828
],
2929
branches: [],
30+
tagsList: [],
3031
model: {
3132
selectedHistoryFile: null
3233
} as GitExtension,

src/__tests__/test-components/PastCommitNode.spec.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,27 @@ describe('PastCommitNode', () => {
4848
}
4949
];
5050
const branches: Git.IBranch[] = notMatchingBranches.concat(matchingBranches);
51+
const matchingTags: Git.ITag[] = [
52+
{
53+
name: '1.0.0',
54+
baseCommitId: '2414721b194453f058079d897d13c4e377f92dc6'
55+
},
56+
{
57+
name: 'feature-1',
58+
baseCommitId: '2414721b194453f058079d897d13c4e377f92dc6'
59+
}
60+
];
61+
const notMatchingTags: Git.ITag[] = [
62+
{
63+
name: 'feature-2',
64+
baseCommitId: '798438398'
65+
},
66+
{
67+
name: 'patch-007',
68+
baseCommitId: '238848848'
69+
}
70+
];
71+
const tags: Git.ITag[] = notMatchingTags.concat(matchingTags);
5172
const toggleCommitExpansion = jest.fn();
5273
const props: IPastCommitNodeProps = {
5374
model: null,
@@ -59,6 +80,7 @@ describe('PastCommitNode', () => {
5980
pre_commits: ['pre_commit']
6081
},
6182
branches: branches,
83+
tagsList: tags,
6284
commands: null,
6385
trans,
6486
onCompareWithSelected: null,
@@ -84,6 +106,14 @@ describe('PastCommitNode', () => {
84106
expect(node.text()).not.toMatch('name2');
85107
});
86108

109+
test('Includes only relevant tag info', () => {
110+
const node = shallow(<PastCommitNode {...props} />);
111+
expect(node.text()).toMatch('1.0.0');
112+
expect(node.text()).toMatch('feature-1');
113+
expect(node.text()).not.toMatch('feature-2');
114+
expect(node.text()).not.toMatch('patch-007');
115+
});
116+
87117
test('Toggle show details', () => {
88118
// simulates SinglePastCommitInfo child
89119
const node = shallow(

src/__tests__/test-components/TagMenu.spec.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,20 @@ jest.mock('@jupyterlab/apputils');
2020

2121
const TAGS = [
2222
{
23-
name: '1.0.0'
23+
name: '1.0.0',
24+
baseCommitId: '4738782743'
2425
},
2526
{
26-
name: 'feature-1'
27+
name: 'feature-1',
28+
baseCommitId: '7432743264'
2729
},
2830
{
29-
name: 'feature-2'
31+
name: 'feature-2',
32+
baseCommitId: '798438398'
3033
},
3134
{
32-
name: 'patch-007'
35+
name: 'patch-007',
36+
baseCommitId: '238848848'
3337
}
3438
];
3539

@@ -78,7 +82,7 @@ describe('TagMenu', () => {
7882
pastCommits: [],
7983
logger: new Logger(),
8084
model: model as IGitExtension,
81-
tagsList: TAGS.map(tag => tag.name),
85+
tagsList: TAGS,
8286
trans: trans,
8387
...props
8488
};

src/components/GitPanel.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export interface IGitPanelState {
9393
/**
9494
* List of tags.
9595
*/
96-
tagsList: string[];
96+
tagsList: Git.ITag[];
9797

9898
/**
9999
* List of changed files.
@@ -598,6 +598,7 @@ export class GitPanel extends React.Component<IGitPanelProps, IGitPanelState> {
598598
<React.Fragment>
599599
<HistorySideBar
600600
branches={this.state.branches}
601+
tagsList={this.state.tagsList}
601602
commits={this.state.pastCommits}
602603
model={this.props.model}
603604
commands={this.props.commands}

src/components/HistorySideBar.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ export interface IHistorySideBarProps {
3232
*/
3333
branches: Git.IBranch[];
3434

35+
/**
36+
* List of tags.
37+
*/
38+
tagsList: Git.ITag[];
39+
3540
/**
3641
* Git extension data model.
3742
*/
@@ -175,6 +180,7 @@ export const HistorySideBar: React.FunctionComponent<IHistorySideBarProps> = (
175180
const commonProps = {
176181
commit,
177182
branches: props.branches,
183+
tagsList: props.tagsList,
178184
model: props.model,
179185
commands: props.commands,
180186
trans: props.trans

src/components/NewTagDialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,14 +369,14 @@ export const NewTagDialogBox: React.FunctionComponent<INewTagDialogProps> = (
369369
*/
370370
const createTag = async (): Promise<void> => {
371371
const tagName = nameState;
372-
const commitId = baseCommitIdState;
372+
const baseCommitId = baseCommitIdState;
373373

374374
props.logger.log({
375375
level: Level.RUNNING,
376376
message: props.trans.__('Creating tag…')
377377
});
378378
try {
379-
await props.model.setTag(tagName, commitId);
379+
await props.model.setTag(tagName, baseCommitId);
380380
} catch (err) {
381381
setErrorState(err.message.replace(/^fatal:/, ''));
382382
props.logger.log({

src/components/PastCommitNode.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ export interface IPastCommitNodeProps {
4242
*/
4343
branches: Git.IBranch[];
4444

45+
/**
46+
* List of tags.
47+
*/
48+
tagsList: Git.ITag[];
49+
4550
/**
4651
* Extension data model.
4752
*/
@@ -199,6 +204,7 @@ export class PastCommitNode extends React.Component<
199204
)}
200205
</div>
201206
<div className={branchWrapperClass}>{this._renderBranches()}</div>
207+
<div className={branchWrapperClass}>{this._renderTags()}</div>
202208
<div className={commitBodyClass}>
203209
{this.props.commit.commit_msg}
204210
{this.props.expanded && this.props.children}
@@ -250,6 +256,39 @@ export class PastCommitNode extends React.Component<
250256
);
251257
}
252258

259+
/**
260+
* Renders tags information.
261+
*
262+
* @returns array of React elements
263+
*/
264+
private _renderTags(): React.ReactElement[] {
265+
const curr = this.props.commit.commit;
266+
const tags: Git.ITag[] = [];
267+
for (let i = 0; i < this.props.tagsList.length; i++) {
268+
const tag = this.props.tagsList[i];
269+
if (tag.baseCommitId && tag.baseCommitId === curr) {
270+
tags.push(tag);
271+
}
272+
}
273+
return tags.map(this._renderTag, this);
274+
}
275+
276+
/**
277+
* Renders individual tag data.
278+
*
279+
* @param tag - tag data
280+
* @returns React element
281+
*/
282+
private _renderTag(tag: Git.ITag): React.ReactElement {
283+
return (
284+
<React.Fragment key={tag.name}>
285+
<span className={classes(branchClass, localBranchClass)}>
286+
{tag.name}
287+
</span>
288+
</React.Fragment>
289+
);
290+
}
291+
253292
/**
254293
* Callback invoked upon clicking on an individual commit.
255294
*

0 commit comments

Comments
 (0)