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

Load gitignore file (#1273) #1298

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 37 additions & 0 deletions jupyterlab_git/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -1688,6 +1688,20 @@ async def remote_remove(self, path, name):

return response

def read_file(self, path):
"""
Reads file content located at path and returns it as a string

path: str
The path of the file
"""
try:
file = pathlib.Path(path)
content = file.read_text()
return {"code": 0, "content": content}
except BaseException as error:
return {"code": -1, "content": ""}

async def ensure_gitignore(self, path):
"""Handle call to ensure .gitignore file exists and the
next append will be on a new line (this means an empty file
Expand Down Expand Up @@ -1728,6 +1742,29 @@ async def ignore(self, path, file_path):
return {"code": -1, "message": str(error)}
return {"code": 0}

async def write_gitignore(self, path, content):
"""
Handle call to overwrite .gitignore.
Takes the .gitignore file and clears its previous contents
Writes the new content onto the file

path: str
Top Git repository path
content: str
New file contents
"""
try:
res = await self.ensure_gitignore(path)
if res["code"] != 0:
return res
gitignore = pathlib.Path(path) / ".gitignore"
if content and content[-1] != "\n":
content += "\n"
gitignore.write_text(content)
except BaseException as error:
return {"code": -1, "message": str(error)}
return {"code": 0}

async def version(self):
"""Return the Git command version.

Expand Down
17 changes: 15 additions & 2 deletions jupyterlab_git/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,17 @@ class GitIgnoreHandler(GitHandler):
Handler to manage .gitignore
"""

@tornado.web.authenticated
async def get(self, path: str = ""):
"""
GET read content in .gitignore
"""
local_path = self.url2localpath(path)
body = self.git.read_file(local_path + "/.gitignore")
if body["code"] != 0:
self.set_status(500)
self.finish(json.dumps(body))

@tornado.web.authenticated
async def post(self, path: str = ""):
"""
Expand All @@ -818,16 +829,18 @@ async def post(self, path: str = ""):
local_path = self.url2localpath(path)
data = self.get_json_body()
file_path = data.get("file_path", None)
content = data.get("content", None)
use_extension = data.get("use_extension", False)
if file_path:
if content:
body = await self.git.write_gitignore(local_path, content)
elif file_path:
if use_extension:
suffixes = Path(file_path).suffixes
if len(suffixes) > 0:
file_path = "**/*" + ".".join(suffixes)
body = await self.git.ignore(local_path, file_path)
else:
body = await self.git.ensure_gitignore(local_path)

if body["code"] != 0:
self.set_status(500)
self.finish(json.dumps(body))
Expand Down
6 changes: 6 additions & 0 deletions schema/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@
"title": "Open files behind warning",
"description": "If true, a popup dialog will be displayed if a user opens a file that is behind its remote branch version, or if an opened file has updates on the remote branch.",
"default": true
},
"hideHiddenFileWarning": {
"type": "boolean",
"title": "Hide hidden file warning",
"description": "If true, the warning popup when opening the .gitignore file without hidden files will not be displayed.",
"default": false
}
},
"jupyter.lab.shortcuts": [
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/commands.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('git-commands', () => {
addCommands(
app as JupyterFrontEnd,
model,
new CodeMirrorEditorFactory().newDocumentEditor,
new CodeMirrorEditorFactory(),
new EditorLanguageRegistry(),
mockedFileBrowserModel,
null as any,
Expand Down
160 changes: 145 additions & 15 deletions src/commandsAndMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,42 @@ import {
showDialog,
showErrorMessage
} from '@jupyterlab/apputils';
import { CodeEditor } from '@jupyterlab/codeeditor';
import {
CodeEditor,
CodeEditorWrapper,
IEditorFactoryService
} from '@jupyterlab/codeeditor';
import { IEditorLanguageRegistry } from '@jupyterlab/codemirror';
import { PathExt, URLExt } from '@jupyterlab/coreutils';
import { FileBrowser, FileBrowserModel } from '@jupyterlab/filebrowser';
import { Contents } from '@jupyterlab/services';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { ITerminal } from '@jupyterlab/terminal';
import { ITranslator, TranslationBundle } from '@jupyterlab/translation';
import {
closeIcon,
ContextMenuSvg,
Toolbar,
ToolbarButton
ToolbarButton,
closeIcon,
saveIcon
} from '@jupyterlab/ui-components';
import { ArrayExt } from '@lumino/algorithm';
import { ArrayExt, find } from '@lumino/algorithm';
import { CommandRegistry } from '@lumino/commands';
import { PromiseDelegate } from '@lumino/coreutils';
import { Message } from '@lumino/messaging';
import { ContextMenu, DockPanel, Menu, Panel, Widget } from '@lumino/widgets';
import * as React from 'react';
import { CancelledError } from './cancelledError';
import { BranchPicker } from './components/BranchPicker';
import { CONTEXT_COMMANDS } from './components/FileList';
import { ManageRemoteDialogue } from './components/ManageRemoteDialogue';
import { NewTagDialogBox } from './components/NewTagDialog';
import { DiffModel } from './components/diff/model';
import { createPlainTextDiff } from './components/diff/PlainTextDiff';
import { PreviewMainAreaWidget } from './components/diff/PreviewMainAreaWidget';
import { CONTEXT_COMMANDS } from './components/FileList';
import { ManageRemoteDialogue } from './components/ManageRemoteDialogue';
import { DiffModel } from './components/diff/model';
import { AUTH_ERROR_MESSAGES, requestAPI } from './git';
import { getDiffProvider, GitExtension } from './model';
import { GitExtension, getDiffProvider } from './model';
import { showDetails, showError } from './notifications';
import {
addIcon,
diffIcon,
Expand All @@ -50,10 +57,8 @@ import {
import { CommandIDs, ContextCommandIDs, Git, IGitExtension } from './tokens';
import { AdvancedPushForm } from './widgets/AdvancedPushForm';
import { GitCredentialsForm } from './widgets/CredentialsBox';
import { discardAllChanges } from './widgets/discardAllChanges';
import { CheckboxForm } from './widgets/GitResetToRemoteForm';
import { IEditorLanguageRegistry } from '@jupyterlab/codemirror';
import { showDetails, showError } from './notifications';
import { discardAllChanges } from './widgets/discardAllChanges';

export interface IGitCloneArgs {
/**
Expand Down Expand Up @@ -126,7 +131,7 @@ function pluralizedContextLabel(singular: string, plural: string) {
export function addCommands(
app: JupyterFrontEnd,
gitModel: GitExtension,
editorFactory: CodeEditor.Factory,
editorFactory: IEditorFactoryService,
languageRegistry: IEditorLanguageRegistry,
fileBrowserModel: FileBrowserModel,
settings: ISettingRegistry.ISettings,
Expand Down Expand Up @@ -314,13 +319,129 @@ export function addCommands(
}
});

async function showGitignore(error: any) {
const model = new CodeEditor.Model({});
const repoPath = gitModel.getRelativeFilePath();
const id = repoPath + '/.git-ignore';
const contentData = await gitModel.readGitIgnore();

const gitIgnoreWidget = find(
shell.widgets(),
shellWidget => shellWidget.id === id
);
if (gitIgnoreWidget) {
shell.activateById(id);
return;
}
model.sharedModel.setSource(contentData ? contentData : '');
const editor = new CodeEditorWrapper({
factory: editorFactory.newDocumentEditor.bind(editorFactory),
model: model
});
const modelChangedSignal = model.sharedModel.changed;
editor.disposed.connect(() => {
model.dispose();
});
const preview = new MainAreaWidget({
content: editor
});

preview.title.label = '.gitignore';
preview.id = id;
preview.title.icon = gitIcon;
preview.title.closable = true;
preview.title.caption = repoPath + '/.gitignore';
const saveButton = new ToolbarButton({
icon: saveIcon,
onClick: async () => {
if (saved) {
return;
}
const newContent = model.sharedModel.getSource();
try {
await gitModel.writeGitIgnore(newContent);
preview.title.className = '';
saved = true;
} catch (error) {
console.log('Could not save .gitignore');
}
},
tooltip: trans.__('Saves .gitignore')
});
let saved = true;
preview.toolbar.addItem('save', saveButton);
shell.add(preview);
modelChangedSignal.connect(() => {
if (saved) {
saved = false;
preview.title.className = 'not-saved';
}
});
}

/* Helper: Show gitignore hidden file */
async function showGitignoreHiddenFile(error: any, hidePrompt: boolean) {
if (hidePrompt) {
return showGitignore(error);
}
const result = await showDialog({
title: trans.__('Warning: The .gitignore file is a hidden file.'),
body: (
<div>
{trans.__(
'Hidden files by default cannot be accessed with the regular code editor. In order to open the .gitignore file you must:'
)}
<ol>
<li>
{trans.__(
'Print the command below to create a jupyter_server_config.py file with defaults commented out. If you already have the file located in .jupyter, skip this step.'
)}
<div style={{ padding: '0.5rem' }}>
{'jupyter server --generate-config'}
</div>
</li>
<li>
{trans.__(
'Open jupyter_server_config.py, uncomment out the following line and set it to True:'
)}
<div style={{ padding: '0.5rem' }}>
{'c.ContentsManager.allow_hidden = False'}
</div>
</li>
</ol>
</div>
),
buttons: [
Dialog.cancelButton({ label: trans.__('Cancel') }),
Dialog.okButton({ label: trans.__('Show .gitignore file anyways') })
],
checkbox: {
label: trans.__('Do not show this warning again'),
checked: false
}
});
if (result.button.accept) {
settings.set('hideHiddenFileWarning', result.isChecked);
showGitignore(error);
}
}

/** Add git open gitignore command */
commands.addCommand(CommandIDs.gitOpenGitignore, {
label: trans.__('Open .gitignore'),
caption: trans.__('Open .gitignore'),
isEnabled: () => gitModel.pathRepository !== null,
execute: async () => {
await gitModel.ensureGitignore();
try {
await gitModel.ensureGitignore();
} catch (error: any) {
if (error?.name === 'hiddenFile') {
await showGitignoreHiddenFile(
error,
settings.composite['hideHiddenFileWarning'] as boolean
);
}
}
}
});

Expand Down Expand Up @@ -586,7 +707,7 @@ export function addCommands(
(options =>
createPlainTextDiff({
...options,
editorFactory,
editorFactory: editorFactory.newInlineEditor.bind(editorFactory),
languageRegistry
})));

Expand Down Expand Up @@ -1523,7 +1644,16 @@ export function addCommands(
const { files } = args as any as CommandArguments.IGitContextAction;
for (const file of files) {
if (file) {
await gitModel.ignore(file.to, false);
try {
await gitModel.ignore(file.to, false);
} catch (error: any) {
if (error?.name === 'hiddenFile') {
await showGitignoreHiddenFile(
error,
settings.composite['hideHiddenFileWarning'] as boolean
);
}
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/diff/PreviewMainAreaWidget.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MainAreaWidget } from '@jupyterlab/apputils/lib/mainareawidget';
import { MainAreaWidget } from '@jupyterlab/apputils';
import { Message } from '@lumino/messaging';
import { Panel, TabBar, Widget } from '@lumino/widgets';

Expand Down
4 changes: 1 addition & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,7 @@ async function activate(
addCommands(
app,
gitExtension,
editorServices.factoryService.newInlineEditor.bind(
editorServices.factoryService
),
editorServices.factoryService,
languageRegistry,
fileBrowser.model,
settings,
Expand Down
Loading
Loading