Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote changes notification #962

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2472592
adds Remote changed section to git panel file list
andrewfulton9 Jun 7, 2021
03aaf77
adds code to add remote upstream changes to files list to report status
andrewfulton9 Jun 12, 2021
2e58a65
changes status symbol for remote changes from U to B for behind remote
andrewfulton9 Jun 16, 2021
e3f4008
moves remote changed files information from model.status.files and
andrewfulton9 Sep 16, 2021
2f8e64d
linting
andrewfulton9 Sep 16, 2021
723ca3f
merge
andrewfulton9 Sep 16, 2021
e140fbe
merge
andrewfulton9 Sep 17, 2021
eb860f1
seperates pulling from remote branch from notifying if a file has
andrewfulton9 Sep 17, 2021
c3dfd0b
fix bug
andrewfulton9 Sep 17, 2021
53870ff
lint and prepare
andrewfulton9 Sep 17, 2021
372b47e
Update src/components/FileList.tsx
andrewfulton9 Sep 21, 2021
117287a
Update src/components/FileList.tsx
andrewfulton9 Sep 21, 2021
59da778
Update src/components/FileList.tsx
andrewfulton9 Sep 21, 2021
731c6e9
Update src/components/FileList.tsx
andrewfulton9 Sep 21, 2021
b9c2a67
Update src/model.ts
andrewfulton9 Sep 21, 2021
0e1e1b3
Update src/model.ts
andrewfulton9 Sep 21, 2021
6b6e7c8
Update src/model.ts
andrewfulton9 Sep 21, 2021
0d784be
changes remote changes section git pull button command from model.pull
andrewfulton9 Sep 21, 2021
970de82
Merge remote-tracking branch 'origin/remote_changes_notification' int…
andrewfulton9 Sep 21, 2021
fd4b62f
wraps remote changed files logic in try catch
andrewfulton9 Sep 21, 2021
962384b
changes to improve remote behind dialog
andrewfulton9 Oct 4, 2021
8501837
moves model.remoteChangedFiles returns
andrewfulton9 Oct 5, 2021
5f89691
Merge remote-tracking branch 'upstream/master' into remote_changes_no…
andrewfulton9 Oct 5, 2021
003f3e4
Update src/components/GitPanel.tsx
andrewfulton9 Oct 7, 2021
5973b61
Update src/components/GitPanel.tsx
andrewfulton9 Oct 7, 2021
615b729
Update src/components/GitPanel.tsx
andrewfulton9 Oct 7, 2021
e7640a5
Update schema/plugin.json
andrewfulton9 Oct 7, 2021
bcbc862
adds this to model.notifyRemoteChanges signal
andrewfulton9 Oct 7, 2021
6b7e8af
defines types in model.checkRemoteChangeNotified
andrewfulton9 Oct 7, 2021
34d1794
Merge branch 'remote_changes_notification' of github.com:andrewfulton…
andrewfulton9 Oct 7, 2021
d1733f3
changes
andrewfulton9 Oct 8, 2021
396ab51
Merge remote-tracking branch 'upstream/master' into remote_changes_no…
andrewfulton9 Oct 8, 2021
4f913b9
fix file.status undefined error
andrewfulton9 Oct 8, 2021
1742bc0
fixes test error and linting
andrewfulton9 Oct 8, 2021
ac56b4b
fix growing repeated list of remote behind
andrewfulton9 Oct 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/FileItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const STATUS_CODES = {
R: 'Renamed',
C: 'Copied',
U: 'Updated',
B: 'Behind',
'?': 'Untracked',
'!': 'Ignored'
};
Expand Down
93 changes: 93 additions & 0 deletions src/components/FileList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const CONTEXT_COMMANDS: ContextCommands = {
ContextCommandIDs.gitFileDiff,
ContextCommandIDs.gitFileHistory
],
'remote-changed': [ContextCommandIDs.gitFileOpen],
unstaged: [
ContextCommandIDs.gitFileOpen,
ContextCommandIDs.gitFileStage,
Expand Down Expand Up @@ -90,6 +91,10 @@ const SIMPLE_CONTEXT_COMMANDS: ContextCommands = {
ContextCommandIDs.gitFileDiff,
ContextCommandIDs.gitFileHistory
],
'remote-changed': [
ContextCommandIDs.gitFileOpen,
ContextCommandIDs.gitFileDiff
andrewfulton9 marked this conversation as resolved.
Show resolved Hide resolved
],
staged: [
ContextCommandIDs.gitFileOpen,
ContextCommandIDs.gitFileDiscard,
Expand Down Expand Up @@ -257,6 +262,11 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
this.setState({ selectedFile: file });
};

pullFromRemote = async (event: React.MouseEvent): Promise<void> => {
event.stopPropagation();
await this.props.model.pull();
andrewfulton9 marked this conversation as resolved.
Show resolved Hide resolved
};

get markedFiles(): Git.IStatusFile[] {
return this.props.files.filter(file => this.props.model.getMark(file.to));
}
Expand All @@ -277,6 +287,7 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
const stagedFiles: Git.IStatusFile[] = [];
const unstagedFiles: Git.IStatusFile[] = [];
const untrackedFiles: Git.IStatusFile[] = [];
const remoteChangedFiles: Git.IStatusFile[] = [];
const unmergedFiles: Git.IStatusFile[] = [];

this.props.files.forEach(file => {
Expand All @@ -303,6 +314,9 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
case 'unmerged':
unmergedFiles.push(file);
break;
case 'remote-changed':
remoteChangedFiles.push(file);
break;
default:
break;
}
Expand All @@ -320,6 +334,7 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
{this._renderStaged(stagedFiles, height)}
{this._renderChanged(unstagedFiles, height)}
{this._renderUntracked(untrackedFiles, height)}
{this._renderRemoteChanged(remoteChangedFiles, height)}
andrewfulton9 marked this conversation as resolved.
Show resolved Hide resolved
</>
)}
</AutoSizer>
Expand Down Expand Up @@ -665,6 +680,84 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
);
}

/**
* Render a untracked file.
*
* Note: This is actually a React.FunctionComponent but defined as
* a private method as it needs access to FileList properties.
*
* @param rowProps Row properties
*/
private _renderRemoteChangedRow = (
rowProps: ListChildComponentProps
): JSX.Element => {
const doubleClickDiff = this.props.settings.get('doubleClickDiff')
.composite as boolean;
const { data, index, style } = rowProps;
const file = data[index] as Git.IStatusFile;
// const diffButton = this._createDiffButton(file);
andrewfulton9 marked this conversation as resolved.
Show resolved Hide resolved
return (
<FileItem
trans={this.props.trans}
actions={
<React.Fragment>
{/** diffButton */}
andrewfulton9 marked this conversation as resolved.
Show resolved Hide resolved
<ActionButton
className={hiddenButtonStyle}
icon={openIcon}
title={this.props.trans.__('Open this file')}
onClick={() => {
this.props.commands.execute(ContextCommandIDs.gitFileOpen, {
files: [file]
} as CommandArguments.IGitContextAction as any);
}}
/>
</React.Fragment>
}
file={file}
contextMenu={this.openContextMenu}
model={this.props.model}
onDoubleClick={() => {
if (!doubleClickDiff) {
this.props.commands.execute(ContextCommandIDs.gitFileOpen, {
files: [file]
} as CommandArguments.IGitContextAction as any);
}
}}
selected={this._isSelectedFile(file)}
selectFile={this.updateSelectedFile}
style={style}
/>
);
};

/**
* Render the a file that has changed on remote to files list.
*
* @param files Untracked files
* @param height Height of the HTML element
*/
private _renderRemoteChanged(files: Git.IStatusFile[], height: number) {
return (
<GitStage
actions={
<ActionButton
className={hiddenButtonStyle}
disabled={files.length === 0}
icon={addIcon}
title={this.props.trans.__('Pull from remote branch')}
onClick={this.pullFromRemote}
/>
}
collapsible
heading={this.props.trans.__('Remote Changes')}
height={height}
files={files}
rowRenderer={this._renderRemoteChangedRow}
/>
);
}

/**
* Render a modified file in simple mode.
*
Expand Down
15 changes: 10 additions & 5 deletions src/components/GitPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export interface IGitPanelState {
*/
files: Git.IStatusFile[];

remoteChangedFiles: Git.IStatusFile[];

/**
* Number of commits ahead
*/
Expand Down Expand Up @@ -140,6 +142,7 @@ export class GitPanel extends React.Component<IGitPanelProps, IGitPanelState> {
branches: branches,
currentBranch: currentBranch ? currentBranch.name : 'master',
files: [],
remoteChangedFiles: [],
nCommitsAhead: 0,
nCommitsBehind: 0,
pastCommits: [],
Expand All @@ -163,9 +166,11 @@ export class GitPanel extends React.Component<IGitPanelProps, IGitPanelState> {
});
this.refreshView();
}, this);
model.statusChanged.connect(() => {
model.statusChanged.connect(async () => {
const remotechangedFiles = await model.remoteChangedFiles();
this.setState({
files: model.status.files,
remoteChangedFiles: remotechangedFiles,
nCommitsAhead: model.status.ahead,
nCommitsBehind: model.status.behind
});
Expand Down Expand Up @@ -651,10 +656,10 @@ export class GitPanel extends React.Component<IGitPanelProps, IGitPanelState> {
* List of sorted modified files.
*/
private get _sortedFiles(): Git.IStatusFile[] {
const { files } = this.state;

files.sort((a, b) => a.to.localeCompare(b.to));
return files;
const { files, remoteChangedFiles } = this.state;
const sfiles = files.concat(remoteChangedFiles);
andrewfulton9 marked this conversation as resolved.
Show resolved Hide resolved
sfiles.sort((a, b) => a.to.localeCompare(b.to));
return sfiles;
}

private _previousRepoPath: string = null;
Expand Down
89 changes: 81 additions & 8 deletions src/model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IChangedArgs, PathExt, URLExt } from '@jupyterlab/coreutils';
import { showDialog, Dialog } from '@jupyterlab/apputils';
import { IDocumentManager } from '@jupyterlab/docmanager';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
Expand Down Expand Up @@ -909,19 +910,19 @@ export class GitExtension implements IGitExtension {
);
}
);

const files = data.files?.map(file => {
return {
...file,
status: decodeStage(file.x, file.y),
type: this._resolveFileType(file.to)
};
});
this._setStatus({
branch: data.branch || null,
remote: data.remote || null,
ahead: data.ahead || 0,
behind: data.behind || 0,
files: data.files?.map(file => {
return {
...file,
status: decodeStage(file.x, file.y),
type: this._resolveFileType(file.to)
};
})
files: files
andrewfulton9 marked this conversation as resolved.
Show resolved Hide resolved
});
} catch (err) {
// TODO we should notify the user
Expand All @@ -931,6 +932,75 @@ export class GitExtension implements IGitExtension {
}
}

/**
* Collects files that have changed on the remote branch.
*
*/
async remoteChangedFiles(): Promise<Git.IStatusFile[]> {
// if a file is changed on remote add it to list of files with appropriate status.
this._remoteChangedFiles = [];
let remoteChangedFiles: null | string[] = null;
if (this.status.remote && this.status.behind > 0) {
remoteChangedFiles = (
await this._changedFiles('WORKING', this.status.remote)
).files;
remoteChangedFiles?.forEach(element => {
this._remoteChangedFiles.push({
status: 'remote-changed',
type: this._resolveFileType(element),
x: '?',
y: 'B',
to: element,
from: '?',
is_binary: false
});
});
andrewfulton9 marked this conversation as resolved.
Show resolved Hide resolved
}
return this._remoteChangedFiles;
}

/**
* Notifies user is a file that is attached has is behind changes in the remote branch with a pop-up Dialog
andrewfulton9 marked this conversation as resolved.
Show resolved Hide resolved
*
*/
async checkRemoteChangeNotified(): Promise<void> {
andrewfulton9 marked this conversation as resolved.
Show resolved Hide resolved
if (this.status.remote && this.status.behind > 0) {
for (const val of this._remoteChangedFiles) {
const docWidget = this._docmanager.findWidget(
this.getRelativeFilePath(val.to)
);
const notifiedIndex = this._changeUpstreamNotified.findIndex(
notified =>
notified.from === val.from &&
notified.to === val.to &&
notified.x === val.x &&
notified.y === val.y
);
if (docWidget !== undefined) {
if (docWidget.isAttached) {
// notify if the user hasn't been notified yet
if (notifiedIndex === -1) {
showDialog({
title: `${val.to} is out of date with your remote branch: ${this._currentBranch.upstream}`,
body: `You may want to pull from ${this._currentBranch.upstream} before editing this file.`,
andrewfulton9 marked this conversation as resolved.
Show resolved Hide resolved
buttons: [Dialog.okButton({ label: 'OK' })]
andrewfulton9 marked this conversation as resolved.
Show resolved Hide resolved
});
// add the file to the notified array
this._changeUpstreamNotified.push(val);
}
}
} else {
// remove from notified array if document is closed
if (notifiedIndex > -1) {
this._changeUpstreamNotified.splice(notifiedIndex, 1);
}
}
}
} else {
this._changeUpstreamNotified = [];
}
}

/**
* Move files from the "staged" to the "unstaged" area.
*
Expand Down Expand Up @@ -1367,6 +1437,7 @@ export class GitExtension implements IGitExtension {
try {
await this.refreshBranch();
await this.refreshStatus();
await this.checkRemoteChangeNotified();
} catch (error) {
console.error('Failed to refresh git status', error);
}
Expand Down Expand Up @@ -1427,6 +1498,8 @@ export class GitExtension implements IGitExtension {
private _standbyCondition: () => boolean = () => false;
private _statusPoll: Poll;
private _taskHandler: TaskHandler<IGitExtension>;
private _remoteChangedFiles: Git.IStatusFile[] = [];
private _changeUpstreamNotified: Git.IStatusFile[] = [];
private _selectedHistoryFile: Git.IStatusFile | null = null;

private _headChanged = new Signal<IGitExtension, void>(this);
Expand Down
11 changes: 11 additions & 0 deletions src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,16 @@ export interface IGitExtension extends IDisposable {
*/
refreshStatus(): Promise<void>;

/**
* gets a list of files that have changed in the remote branch
*/
remoteChangedFiles(): Promise<Git.IStatusFile[]>;

/**
* Notifies user is a file that is attached has is behind changes in the remote branch with a pop-up Dialog
*/
checkRemoteChangeNotified(): Promise<void>;

/**
* Register a new diff provider for specified file types
*
Expand Down Expand Up @@ -894,6 +904,7 @@ export namespace Git {
| 'staged'
| 'unstaged'
| 'partially-staged'
| 'remote-changed'
| 'unmodified'
| 'unmerged'
| null;
Expand Down