UNPKG

brackets-git

Version:
1,177 lines (1,024 loc) 48.7 kB
/*jshint maxstatements:false*/ define(function (require, exports) { "use strict"; var moment = require("moment"), Promise = require("bluebird"), _ = brackets.getModule("thirdparty/lodash"), CodeInspection = brackets.getModule("language/CodeInspection"), CommandManager = brackets.getModule("command/CommandManager"), Commands = brackets.getModule("command/Commands"), Dialogs = brackets.getModule("widgets/Dialogs"), DocumentManager = brackets.getModule("document/DocumentManager"), EditorManager = brackets.getModule("editor/EditorManager"), FileViewController = brackets.getModule("project/FileViewController"), KeyBindingManager = brackets.getModule("command/KeyBindingManager"), FileSystem = brackets.getModule("filesystem/FileSystem"), Menus = brackets.getModule("command/Menus"), FindInFiles = brackets.getModule("search/FindInFiles"), WorkspaceManager = brackets.getModule("view/WorkspaceManager"), ProjectManager = brackets.getModule("project/ProjectManager"), StringUtils = brackets.getModule("utils/StringUtils"), Git = require("src/git/Git"), Events = require("./Events"), EventEmitter = require("./EventEmitter"), Preferences = require("./Preferences"), ErrorHandler = require("./ErrorHandler"), ExpectedError = require("./ExpectedError"), Main = require("./Main"), GutterManager = require("./GutterManager"), Strings = require("../strings"), Utils = require("src/Utils"), SettingsDialog = require("./SettingsDialog"), ProgressDialog = require("src/dialogs/Progress"), PANEL_COMMAND_ID = "brackets-git.panel"; var gitPanelTemplate = require("text!templates/git-panel.html"), gitPanelResultsTemplate = require("text!templates/git-panel-results.html"), gitAuthorsDialogTemplate = require("text!templates/authors-dialog.html"), gitCommitDialogTemplate = require("text!templates/git-commit-dialog.html"), gitDiffDialogTemplate = require("text!templates/git-diff-dialog.html"), questionDialogTemplate = require("text!templates/git-question-dialog.html"); var showFileWhiteList = /^\.gitignore$/; var gitPanel = null, $gitPanel = $(null), gitPanelDisabled = null, gitPanelMode = null, showingUntracked = true, $tableContainer = $(null), lastCommitMessage = null; function lintFile(filename) { var fullPath = Preferences.get("currentGitRoot") + filename, codeInspectionPromise; try { codeInspectionPromise = CodeInspection.inspectFile(FileSystem.getFileForPath(fullPath)); } catch (e) { ErrorHandler.logError("CodeInspection.inspectFile failed to execute for file " + fullPath); ErrorHandler.logError(e); codeInspectionPromise = Promise.reject(e); } return Promise.cast(codeInspectionPromise); } function _makeDialogBig($dialog) { var $wrapper = $dialog.parents(".modal-wrapper").first(); if ($wrapper.length === 0) { return; } // We need bigger commit dialog var minWidth = 500, minHeight = 300, maxWidth = $wrapper.width(), maxHeight = $wrapper.height(), desiredWidth = maxWidth / 2, desiredHeight = maxHeight / 2; if (desiredWidth < minWidth) { desiredWidth = minWidth; } if (desiredHeight < minHeight) { desiredHeight = minHeight; } $dialog .width(desiredWidth) .children(".modal-body") .css("max-height", desiredHeight) .end(); return { width: desiredWidth, height: desiredHeight }; } function _showCommitDialog(stagedDiff, lintResults, prefilledMessage) { lintResults = lintResults || []; // Flatten the error structure from various providers lintResults.forEach(function (lintResult) { lintResult.errors = []; if (Array.isArray(lintResult.result)) { lintResult.result.forEach(function (resultSet) { if (!resultSet.result || !resultSet.result.errors) { return; } var providerName = resultSet.provider.name; resultSet.result.errors.forEach(function (e) { lintResult.errors.push((e.pos.line + 1) + ": " + e.message + " (" + providerName + ")"); }); }); } else { ErrorHandler.logError("[brackets-git] lintResults contain object in unexpected format: " + JSON.stringify(lintResult)); } lintResult.hasErrors = lintResult.errors.length > 0; }); // Filter out only results with errors to show lintResults = _.filter(lintResults, function (lintResult) { return lintResult.hasErrors; }); // Open the dialog var compiledTemplate = Mustache.render(gitCommitDialogTemplate, { Strings: Strings, hasLintProblems: lintResults.length > 0, lintResults: lintResults }), dialog = Dialogs.showModalDialogUsingTemplate(compiledTemplate), $dialog = dialog.getElement(); // We need bigger commit dialog _makeDialogBig($dialog); // Show nicely colored commit diff $dialog.find(".commit-diff").append(Utils.formatDiff(stagedDiff)); // Enable / Disable amend checkbox var toggleAmendCheckbox = function (bool) { $dialog.find(".amend-commit") .prop("disabled", !bool) .parent() .attr("title", !bool ? Strings.AMEND_COMMIT_FORBIDDEN : null); }; toggleAmendCheckbox(false); Git.getCommitsAhead().then(function (commits) { toggleAmendCheckbox(commits.length > 0); }); function getCommitMessageElement() { var r = $dialog.find("[name='commit-message']:visible"); if (r.length !== 1) { r = $dialog.find("[name='commit-message']"); for (var i = 0; i < r.length; i++) { if ($(r[i]).css("display") !== "none") { return $(r[i]); } } } return r; } var $commitMessageCount = $dialog.find("input[name='commit-message-count']"); // Add event to count characters in commit message var recalculateMessageLength = function () { var val = getCommitMessageElement().val().trim(), length = val.length; if (val.indexOf("\n")) { // longest line length = Math.max.apply(null, val.split("\n").map(function (l) { return l.length; })); } $commitMessageCount .val(length) .toggleClass("over50", length > 50 && length <= 100) .toggleClass("over100", length > 100); }; var usingTextArea = false; // commit message handling function switchCommitMessageElement() { usingTextArea = !usingTextArea; var findStr = "[name='commit-message']", currentValue = $dialog.find(findStr + ":visible").val(); $dialog.find(findStr).toggle(); $dialog.find(findStr + ":visible") .val(currentValue) .focus(); recalculateMessageLength(); } $dialog.find("button.primary").on("click", function (e) { var $commitMessage = getCommitMessageElement(); if ($commitMessage.val().trim().length === 0) { e.stopPropagation(); $commitMessage.addClass("invalid"); } else { $commitMessage.removeClass("invalid"); } }); $dialog.find("button.extendedCommit").on("click", function () { switchCommitMessageElement(); // this value will be set only when manually triggered Preferences.set("useTextAreaForCommitByDefault", usingTextArea); }); function prefillMessage(msg) { if (msg.indexOf("\n") !== -1 && !usingTextArea) { switchCommitMessageElement(); } $dialog.find("[name='commit-message']:visible").val(msg); recalculateMessageLength(); } // Assign action to amend checkbox $dialog.find(".amend-commit").on("click", function () { if ($(this).prop("checked") === false) { prefillMessage(""); } else { Git.getLastCommitMessage().then(function (msg) { prefillMessage(msg); }); } }); if (Preferences.get("useTextAreaForCommitByDefault")) { switchCommitMessageElement(); } if (prefilledMessage) { prefillMessage(prefilledMessage.trim()); } // Add focus to commit message input getCommitMessageElement().focus(); $dialog.find("[name='commit-message']") .on("keyup", recalculateMessageLength) .on("change", recalculateMessageLength); recalculateMessageLength(); dialog.done(function (buttonId) { if (buttonId === "ok") { // this event won't launch when commit-message is empty so its safe to assume that it is not var commitMessage = getCommitMessageElement().val(), amendCommit = $dialog.find(".amend-commit").prop("checked"); // if commit message is extended and has a newline, put an empty line after first line to separate subject and body var s = commitMessage.split("\n"); if (s.length > 1 && s[1].trim() !== "") { s.splice(1, 0, ""); } commitMessage = s.join("\n"); // save lastCommitMessage in case the commit will fail lastCommitMessage = commitMessage; // now we are going to be paranoid and we will check if some mofo didn't change our diff _getStagedDiff().then(function (diff) { if (diff === stagedDiff) { return Git.commit(commitMessage, amendCommit).then(function () { // clear lastCommitMessage because the commit was successful lastCommitMessage = null; }); } else { throw new ExpectedError("The files you were going to commit were modified while commit dialog was displayed. " + "Aborting the commit as the result would be different then what was shown in the dialog."); } }).catch(function (err) { if (ErrorHandler.contains(err, "Please tell me who you are")) { var defer = Promise.defer(); EventEmitter.emit(Events.GIT_CHANGE_USERNAME, null, function () { EventEmitter.emit(Events.GIT_CHANGE_EMAIL, null, function () { defer.resolve(); }); }); return defer.promise; } ErrorHandler.showError(err, "Git Commit failed"); }).finally(function () { EventEmitter.emit(Events.GIT_COMMITED); refresh(); }); } else { // this will trigger refreshing where appropriate Git.status(); } }); } function _showAuthors(file, blame, fromLine, toLine) { var linesTotal = blame.length; var blameStats = blame.reduce(function (stats, lineInfo) { var name = lineInfo.author + " " + lineInfo["author-mail"]; if (stats[name]) { stats[name] += 1; } else { stats[name] = 1; } return stats; }, {}); blameStats = _.reduce(blameStats, function (arr, val, key) { arr.push({ authorName: key, lines: val, percentage: Math.round(val / (linesTotal / 100)) }); return arr; }, []); blameStats = _.sortBy(blameStats, "lines").reverse(); if (fromLine || toLine) { file += " (" + Strings.LINES + " " + fromLine + "-" + toLine + ")"; } var compiledTemplate = Mustache.render(gitAuthorsDialogTemplate, { file: file, blameStats: blameStats, Strings: Strings }); Dialogs.showModalDialogUsingTemplate(compiledTemplate); } function _getCurrentFilePath(editor) { var gitRoot = Preferences.get("currentGitRoot"), document = editor ? editor.document : DocumentManager.getCurrentDocument(), filePath = document.file.fullPath; if (filePath.indexOf(gitRoot) === 0) { filePath = filePath.substring(gitRoot.length); } return filePath; } function handleAuthorsSelection() { var editor = EditorManager.getActiveEditor(), filePath = _getCurrentFilePath(editor), currentSelection = editor.getSelection(), fromLine = currentSelection.start.line + 1, toLine = currentSelection.end.line + 1; // fix when nothing is selected on that line if (currentSelection.end.ch === 0) { toLine = toLine - 1; } var isSomethingSelected = currentSelection.start.line !== currentSelection.end.line || currentSelection.start.ch !== currentSelection.end.ch; if (!isSomethingSelected) { ErrorHandler.showError(new ExpectedError(Strings.ERROR_NOTHING_SELECTED)); return; } if (editor.document.isDirty) { ErrorHandler.showError(new ExpectedError(Strings.ERROR_SAVE_FIRST)); return; } Git.getBlame(filePath, fromLine, toLine).then(function (blame) { return _showAuthors(filePath, blame, fromLine, toLine); }).catch(function (err) { ErrorHandler.showError(err, "Git Blame failed"); }); } function handleAuthorsFile() { var filePath = _getCurrentFilePath(); Git.getBlame(filePath).then(function (blame) { return _showAuthors(filePath, blame); }).catch(function (err) { ErrorHandler.showError(err, "Git Blame failed"); }); } function handleGitDiff(file) { if (Preferences.get("useDifftool")) { Git.difftool(file); } else { Git.diffFileNice(file).then(function (diff) { // show the dialog with the diff var compiledTemplate = Mustache.render(gitDiffDialogTemplate, { file: file, Strings: Strings }), dialog = Dialogs.showModalDialogUsingTemplate(compiledTemplate), $dialog = dialog.getElement(); _makeDialogBig($dialog); $dialog.find(".commit-diff").append(Utils.formatDiff(diff)); }).catch(function (err) { ErrorHandler.showError(err, "Git Diff failed"); }); } } function handleGitUndo(file) { var compiledTemplate = Mustache.render(questionDialogTemplate, { title: Strings.UNDO_CHANGES, question: StringUtils.format(Strings.Q_UNDO_CHANGES, _.escape(file)), Strings: Strings }); Dialogs.showModalDialogUsingTemplate(compiledTemplate).done(function (buttonId) { if (buttonId === "ok") { Git.discardFileChanges(file).then(function () { var gitRoot = Preferences.get("currentGitRoot"); DocumentManager.getAllOpenDocuments().forEach(function (doc) { if (doc.file.fullPath === gitRoot + file) { Utils.reloadDoc(doc); } }); refresh(); }).catch(function (err) { ErrorHandler.showError(err, "Discard changes to a file failed"); }); } }); } function handleGitDelete(file) { var compiledTemplate = Mustache.render(questionDialogTemplate, { title: Strings.DELETE_FILE, question: StringUtils.format(Strings.Q_DELETE_FILE, _.escape(file)), Strings: Strings }); Dialogs.showModalDialogUsingTemplate(compiledTemplate).done(function (buttonId) { if (buttonId === "ok") { FileSystem.resolve(Preferences.get("currentGitRoot") + file, function (err, fileEntry) { if (err) { ErrorHandler.showError(err, "Could not resolve file"); return; } Promise.cast(ProjectManager.deleteItem(fileEntry)) .then(function () { refresh(); }) .catch(function (err) { ErrorHandler.showError(err, "File deletion failed"); }); }); } }); } function _getStagedDiff() { return ProgressDialog.show(Git.getDiffOfStagedFiles(), Strings.GETTING_STAGED_DIFF_PROGRESS, { preDelay: 3, postDelay: 1 }) .catch(function (err) { if (ErrorHandler.contains(err, "cleanup")) { return false; // will display list of staged files instead } throw err; }) .then(function (diff) { if (!diff) { return Git.getListOfStagedFiles().then(function (filesList) { return Strings.DIFF_FAILED_SEE_FILES + "\n\n" + filesList; }); } return diff; }); } // whatToDo gets values "continue" "skip" "abort" function handleRebase(whatToDo) { Git.rebase(whatToDo).then(function () { EventEmitter.emit(Events.REFRESH_ALL); }).catch(function (err) { ErrorHandler.showError(err, "Rebase " + whatToDo + " failed"); }); } function abortMerge() { Git.discardAllChanges().then(function () { EventEmitter.emit(Events.REFRESH_ALL); }).catch(function (err) { ErrorHandler.showError(err, "Merge abort failed"); }); } function findConflicts() { FindInFiles.doSearch(/^<<<<<<<\s|^=======\s|^>>>>>>>\s/gm); } function commitMerge() { Utils.loadPathContent(Preferences.get("currentGitRoot") + "/.git/MERGE_MSG").then(function (msg) { handleGitCommit(msg, true); EventEmitter.once(Events.GIT_COMMITED, function () { EventEmitter.emit(Events.REFRESH_ALL); }); }).catch(function (err) { ErrorHandler.showError(err, "Merge commit failed"); }); } function inspectFiles(gitStatusResults) { var lintResults = []; var codeInspectionPromises = gitStatusResults.map(function (fileObj) { var isDeleted = fileObj.status.indexOf(Git.FILE_STATUS.DELETED) !== -1; // do a code inspection for the file, if it was not deleted if (!isDeleted) { return lintFile(fileObj.file) .catch(function () { return [ { provider: { name: "See console [F12] for details" }, result: { errors: [ { pos: { line: 0, ch: 0 }, message: "CodeInspection failed to execute for this file." } ] } } ]; }) .then(function (result) { if (result) { lintResults.push({ filename: fileObj.file, result: result }); } }); } }); return Promise.all(_.compact(codeInspectionPromises)).then(function () { return lintResults; }); } function handleGitCommit(prefilledMessage, isMerge) { var stripWhitespace = Preferences.get("stripWhitespaceFromCommits"); var codeInspectionEnabled = Preferences.get("useCodeInspection"); // Disable button (it will be enabled when selecting files after reset) Utils.setLoading($gitPanel.find(".git-commit")); // First reset staged files, then add selected files to the index. Git.status().then(function (files) { files = _.filter(files, function (file) { return file.status.indexOf(Git.FILE_STATUS.STAGED) !== -1; }); if (files.length === 0 && !isMerge) { return ErrorHandler.showError(new Error("Commit button should have been disabled"), "Nothing staged to commit"); } var queue = Promise.resolve(); var lintResults; if (stripWhitespace) { queue = queue.then(function () { return ProgressDialog.show(Utils.stripWhitespaceFromFiles(files), Strings.CLEANING_WHITESPACE_PROGRESS, { preDelay: 3, postDelay: 1 }); }); } if (codeInspectionEnabled) { queue = queue.then(function () { return inspectFiles(files).then(function (_lintResults) { lintResults = _lintResults; }); }); } return queue.then(function () { // All files are in the index now, get the diff and show dialog. return _getStagedDiff().then(function (diff) { return _showCommitDialog(diff, lintResults, prefilledMessage); }); }); }).catch(function (err) { ErrorHandler.showError(err, "Preparing commit dialog failed"); }).finally(function () { Utils.unsetLoading($gitPanel.find(".git-commit")); }); } function refreshCurrentFile() { var gitRoot = Preferences.get("currentGitRoot"); var currentDoc = DocumentManager.getCurrentDocument(); if (currentDoc) { $gitPanel.find("tr").each(function () { var currentFullPath = currentDoc.file.fullPath, thisFile = $(this).attr("x-file"); $(this).toggleClass("selected", gitRoot + thisFile === currentFullPath); }); } else { $gitPanel.find("tr").removeClass("selected"); } } function shouldShow(fileObj) { if (showFileWhiteList.test(fileObj.name)) { return true; } return ProjectManager.shouldShow(fileObj); } function _refreshTableContainer(files) { if (!gitPanel.isVisible()) { return; } // remove files that we should not show files = _.filter(files, function (file) { return shouldShow(file); }); var allStaged = files.length > 0 && _.all(files, function (file) { return file.status.indexOf(Git.FILE_STATUS.STAGED) !== -1; }); $gitPanel.find(".check-all").prop("checked", allStaged).prop("disabled", files.length === 0); var $editedList = $tableContainer.find(".git-edited-list"); var visibleBefore = $editedList.length ? $editedList.is(":visible") : true; $editedList.remove(); if (files.length === 0) { $tableContainer.append($("<p class='git-edited-list nothing-to-commit' />").text(Strings.NOTHING_TO_COMMIT)); } else { // if desired, remove untracked files from the results if (showingUntracked === false) { files = _.filter(files, function (file) { return file.status.indexOf(Git.FILE_STATUS.UNTRACKED) === -1; }); } // - files.forEach(function (file) { file.staged = file.status.indexOf(Git.FILE_STATUS.STAGED) !== -1; file.statusText = file.status.map(function (status) { return Strings["FILE_" + status]; }).join(", "); file.allowDiff = file.status.indexOf(Git.FILE_STATUS.UNTRACKED) === -1 && file.status.indexOf(Git.FILE_STATUS.RENAMED) === -1 && file.status.indexOf(Git.FILE_STATUS.DELETED) === -1; file.allowDelete = file.status.indexOf(Git.FILE_STATUS.UNTRACKED) !== -1 || file.status.indexOf(Git.FILE_STATUS.STAGED) !== -1 && file.status.indexOf(Git.FILE_STATUS.ADDED) !== -1; file.allowUndo = !file.allowDelete; }); $tableContainer.append(Mustache.render(gitPanelResultsTemplate, { files: files, Strings: Strings })); refreshCurrentFile(); } $tableContainer.find(".git-edited-list").toggle(visibleBefore); } function refresh() { // set the history panel to false and remove the class that show the button history active when refresh $gitPanel.find(".git-history-toggle").removeClass("active").attr("title", Strings.TOOLTIP_SHOW_HISTORY); $gitPanel.find(".git-file-history").removeClass("active").attr("title", Strings.TOOLTIP_SHOW_FILE_HISTORY); if (gitPanelMode === "not-repo") { $tableContainer.empty(); return Promise.resolve(); } $tableContainer.find("#git-history-list").remove(); $tableContainer.find(".git-edited-list").show(); var p1 = Git.status().catch(function (err) { // this is an expected "error" if (ErrorHandler.contains(err, "Not a git repository")) { return; } }); // Push button var $pushBtn = $gitPanel.find(".git-push"); var p2 = Git.getCommitsAhead().then(function (commits) { $pushBtn.children("span").remove(); if (commits.length > 0) { $pushBtn.append($("<span/>").text(" (" + commits.length + ")")); } }).catch(function () { $pushBtn.children("span").remove(); }); // Clone button $gitPanel.find(".git-clone").prop("disabled", false); // FUTURE: who listens for this? return Promise.all([p1, p2]); } function toggle(bool) { if (gitPanelDisabled === true) { return; } if (typeof bool !== "boolean") { bool = !gitPanel.isVisible(); } Preferences.persist("panelEnabled", bool); Main.$icon.toggleClass("on", bool); gitPanel.setVisible(bool); // Mark menu item as enabled/disabled. CommandManager.get(PANEL_COMMAND_ID).setChecked(bool); if (bool) { refresh(); } } function handleToggleUntracked() { showingUntracked = !showingUntracked; $gitPanel .find(".git-toggle-untracked") .text(showingUntracked ? Strings.HIDE_UNTRACKED : Strings.SHOW_UNTRACKED); refresh(); } function commitCurrentFile() { // do not return anything here, core expects jquery promise Promise.cast(CommandManager.execute("file.save")) .then(function () { return Git.resetIndex(); }) .then(function () { var gitRoot = Preferences.get("currentGitRoot"); var currentDoc = DocumentManager.getCurrentDocument(); if (currentDoc) { var relativePath = currentDoc.file.fullPath.substring(gitRoot.length); return Git.stage(relativePath).then(function () { return handleGitCommit(); }); } }); } function commitAllFiles() { // do not return anything here, core expects jquery promise Promise.cast(CommandManager.execute("file.saveAll")) .then(function () { return Git.resetIndex(); }) .then(function () { return Git.stageAll().then(function () { return handleGitCommit(); }); }); } // Disable "commit" button if there aren't staged files to commit function _toggleCommitButton(files) { var anyStaged = _.any(files, function (file) { return file.status.indexOf(Git.FILE_STATUS.STAGED) !== -1; }); $gitPanel.find(".git-commit").prop("disabled", !anyStaged); } EventEmitter.on(Events.GIT_STATUS_RESULTS, function (results) { _refreshTableContainer(results); _toggleCommitButton(results); }); function undoLastLocalCommit() { Git.undoLastLocalCommit() .catch(function (err) { ErrorHandler.showError(err, "Impossible to undo last commit"); }) .finally(function () { refresh(); }); } var lastCheckOneClicked = null; function attachDefaultTableHandlers() { $tableContainer = $gitPanel.find(".table-container") .off() .on("click", ".check-one", function (e) { e.stopPropagation(); var $tr = $(this).closest("tr"), file = $tr.attr("x-file"), status = $tr.attr("x-status"), isChecked = $(this).is(":checked"); if (e.shiftKey) { // stage/unstage all file between var lc = lastCheckOneClicked.localeCompare(file), lcClickedSelector = "[x-file='" + lastCheckOneClicked + "']", sequence; if (lc < 0) { sequence = $tr.prevUntil(lcClickedSelector).andSelf(); } else if (lc > 0) { sequence = $tr.nextUntil(lcClickedSelector).andSelf(); } if (sequence) { sequence = sequence.add($tr.parent().children(lcClickedSelector)); var promises = sequence.map(function () { var $this = $(this), method = isChecked ? "stage" : "unstage", file = $this.attr("x-file"), status = $this.attr("x-status"); return Git[method](file, status === Git.FILE_STATUS.DELETED); }).toArray(); return Promise.all(promises).then(function () { return Git.status(); }).catch(function (err) { ErrorHandler.showError(err, "Modifying file status failed"); }); } } lastCheckOneClicked = file; if (isChecked) { Git.stage(file, status === Git.FILE_STATUS.DELETED).then(function () { Git.status(); }); } else { Git.unstage(file).then(function () { Git.status(); }); } }) .on("dblclick", ".check-one", function (e) { e.stopPropagation(); }) .on("click", ".btn-git-diff", function (e) { e.stopPropagation(); handleGitDiff($(e.target).closest("tr").attr("x-file")); }) .on("click", ".btn-git-undo", function (e) { e.stopPropagation(); handleGitUndo($(e.target).closest("tr").attr("x-file")); }) .on("click", ".btn-git-delete", function (e) { e.stopPropagation(); handleGitDelete($(e.target).closest("tr").attr("x-file")); }) .on("click", ".modified-file", function (e) { var $this = $(e.currentTarget); if ($this.attr("x-status") === Git.FILE_STATUS.DELETED) { return; } CommandManager.execute(Commands.FILE_OPEN, { fullPath: Preferences.get("currentGitRoot") + $this.attr("x-file") }); }) .on("dblclick", ".modified-file", function (e) { var $this = $(e.currentTarget); if ($this.attr("x-status") === Git.FILE_STATUS.DELETED) { return; } FileViewController.addToWorkingSetAndSelect(Preferences.get("currentGitRoot") + $this.attr("x-file")); }); } EventEmitter.on(Events.GIT_CHANGE_USERNAME, function (event, callback) { return Git.getConfig("user.name").then(function (currentUserName) { return Utils.askQuestion(Strings.CHANGE_USER_NAME, Strings.ENTER_NEW_USER_NAME, { defaultValue: currentUserName }) .then(function (userName) { if (!userName.length) { userName = currentUserName; } return Git.setConfig("user.name", userName, true).catch(function (err) { ErrorHandler.showError(err, "Impossible to change username"); }).then(function () { EventEmitter.emit(Events.GIT_USERNAME_CHANGED, userName); }).finally(function () { if (callback) { callback(userName); } }); }); }); }); EventEmitter.on(Events.GIT_CHANGE_EMAIL, function (event, callback) { return Git.getConfig("user.email").then(function (currentUserEmail) { return Utils.askQuestion(Strings.CHANGE_USER_EMAIL, Strings.ENTER_NEW_USER_EMAIL, { defaultValue: currentUserEmail }) .then(function (userEmail) { if (!userEmail.length) { userEmail = currentUserEmail; } return Git.setConfig("user.email", userEmail, true).catch(function (err) { ErrorHandler.showError(err, "Impossible to change user email"); }).then(function () { EventEmitter.emit(Events.GIT_EMAIL_CHANGED, userEmail); }).finally(function () { if (callback) { callback(userEmail); } }); }); }); }); EventEmitter.on(Events.GERRIT_TOGGLE_PUSH_REF, function (event, callback) { // update preference and emit so the menu item updates return Git.getConfig("gerrit.pushref").then(function (strEnabled) { var toggledValue = strEnabled !== "true"; // Set the global preference // Saving a preference to tell the GitCli.push() method to check for gerrit push ref enablement // so we don't slow down people who aren't using gerrit. Preferences.persist("gerritPushref", toggledValue); return Git.setConfig("gerrit.pushref", toggledValue, true) .then(function () { EventEmitter.emit(Events.GERRIT_PUSH_REF_TOGGLED, toggledValue); }) .finally(function () { if (callback) { callback(toggledValue); } }); }).catch(function (err) { ErrorHandler.showError(err, "Impossible to toggle gerrit push ref"); }); }); EventEmitter.on(Events.GERRIT_PUSH_REF_TOGGLED, function (enabled) { setGerritCheckState(enabled); }); function setGerritCheckState(enabled) { $gitPanel .find(".toggle-gerrit-push-ref") .toggleClass("checkmark", enabled); } function discardAllChanges() { return Utils.askQuestion(Strings.RESET_LOCAL_REPO, Strings.RESET_LOCAL_REPO_CONFIRM, { booleanResponse: true }) .then(function (response) { if (response) { return Git.discardAllChanges().catch(function (err) { ErrorHandler.showError(err, "Reset of local repository failed"); }).then(function () { refresh(); }); } }); } function init() { // Add panel var panelHtml = Mustache.render(gitPanelTemplate, { enableAdvancedFeatures: Preferences.get("enableAdvancedFeatures"), showBashButton: Preferences.get("showBashButton"), showReportBugButton: Preferences.get("showReportBugButton"), S: Strings }); var $panelHtml = $(panelHtml); $panelHtml.find(".git-available, .git-not-available").hide(); gitPanel = WorkspaceManager.createBottomPanel("brackets-git.panel", $panelHtml, 100); $gitPanel = gitPanel.$panel; $gitPanel .on("click", ".close", toggle) .on("click", ".check-all", function () { if ($(this).is(":checked")) { return Git.stageAll().then(function () { Git.status(); }); } else { return Git.resetIndex().then(function () { Git.status(); }); } }) .on("click", ".git-refresh", EventEmitter.emitFactory(Events.REFRESH_ALL)) .on("click", ".git-commit", EventEmitter.emitFactory(Events.HANDLE_GIT_COMMIT)) .on("click", ".git-rebase-continue", function (e) { handleRebase("continue", e); }) .on("click", ".git-rebase-skip", function (e) { handleRebase("skip", e); }) .on("click", ".git-rebase-abort", function (e) { handleRebase("abort", e); }) .on("click", ".git-commit-merge", commitMerge) .on("click", ".git-merge-abort", abortMerge) .on("click", ".git-find-conflicts", findConflicts) .on("click", ".git-prev-gutter", GutterManager.goToPrev) .on("click", ".git-next-gutter", GutterManager.goToNext) .on("click", ".git-toggle-untracked", handleToggleUntracked) .on("click", ".authors-selection", handleAuthorsSelection) .on("click", ".authors-file", handleAuthorsFile) .on("click", ".git-file-history", EventEmitter.emitFactory(Events.HISTORY_SHOW, "FILE")) .on("click", ".git-history-toggle", EventEmitter.emitFactory(Events.HISTORY_SHOW, "GLOBAL")) .on("click", ".git-push", function () { var typeOfRemote = $(this).attr("x-selected-remote-type"); if (typeOfRemote === "git") { EventEmitter.emit(Events.HANDLE_PUSH); } }) .on("click", ".git-pull", EventEmitter.emitFactory(Events.HANDLE_PULL)) .on("click", ".git-bug", ErrorHandler.reportBug) .on("click", ".git-init", EventEmitter.emitFactory(Events.HANDLE_GIT_INIT)) .on("click", ".git-clone", EventEmitter.emitFactory(Events.HANDLE_GIT_CLONE)) .on("click", ".change-remote", EventEmitter.emitFactory(Events.HANDLE_REMOTE_PICK)) .on("click", ".remove-remote", EventEmitter.emitFactory(Events.HANDLE_REMOTE_DELETE)) .on("click", ".git-remote-new", EventEmitter.emitFactory(Events.HANDLE_REMOTE_CREATE)) .on("click", ".git-settings", SettingsDialog.show) .on("contextmenu", "tr", function (e) { var $this = $(this); if ($this.hasClass("history-commit")) { return; } $this.click(); setTimeout(function () { Menus.getContextMenu("git-panel-context-menu").open(e); }, 1); }) .on("click", ".change-user-name", EventEmitter.emitFactory(Events.GIT_CHANGE_USERNAME)) .on("click", ".change-user-email", EventEmitter.emitFactory(Events.GIT_CHANGE_EMAIL)) .on("click", ".toggle-gerrit-push-ref", EventEmitter.emitFactory(Events.GERRIT_TOGGLE_PUSH_REF)) .on("click", ".undo-last-commit", undoLastLocalCommit) .on("click", ".git-bash", EventEmitter.emitFactory(Events.TERMINAL_OPEN)) .on("click", ".reset-all", discardAllChanges); /* Put here event handlers for advanced actions if (Preferences.get("enableAdvancedFeatures")) { $gitPanel .on("click", target, function); } */ // Attaching table handlers attachDefaultTableHandlers(); // Commit current and all shortcuts var COMMIT_CURRENT_CMD = "brackets-git.commitCurrent", COMMIT_ALL_CMD = "brackets-git.commitAll", BASH_CMD = "brackets-git.launchBash", PUSH_CMD = "brackets-git.push", PULL_CMD = "brackets-git.pull", GOTO_PREV_CHANGE = "brackets-git.gotoPrevChange", GOTO_NEXT_CHANGE = "brackets-git.gotoNextChange"; // Add command to menu. // Register command for opening bottom panel. CommandManager.register(Strings.PANEL_COMMAND, PANEL_COMMAND_ID, toggle); KeyBindingManager.addBinding(PANEL_COMMAND_ID, Preferences.get("panelShortcut"), brackets.platform); CommandManager.register(Strings.COMMIT_CURRENT_SHORTCUT, COMMIT_CURRENT_CMD, commitCurrentFile); KeyBindingManager.addBinding(COMMIT_CURRENT_CMD, Preferences.get("commitCurrentShortcut"), brackets.platform); CommandManager.register(Strings.COMMIT_ALL_SHORTCUT, COMMIT_ALL_CMD, commitAllFiles); KeyBindingManager.addBinding(COMMIT_ALL_CMD, Preferences.get("commitAllShortcut"), brackets.platform); CommandManager.register(Strings.LAUNCH_BASH_SHORTCUT, BASH_CMD, EventEmitter.emitFactory(Events.TERMINAL_OPEN)); KeyBindingManager.addBinding(BASH_CMD, Preferences.get("bashShortcut"), brackets.platform); CommandManager.register(Strings.PUSH_SHORTCUT, PUSH_CMD, EventEmitter.emitFactory(Events.HANDLE_PUSH)); KeyBindingManager.addBinding(PUSH_CMD, Preferences.get("pushShortcut"), brackets.platform); CommandManager.register(Strings.PULL_SHORTCUT, PULL_CMD, EventEmitter.emitFactory(Events.HANDLE_PULL)); KeyBindingManager.addBinding(PULL_CMD, Preferences.get("pullShortcut"), brackets.platform); CommandManager.register(Strings.GOTO_PREVIOUS_GIT_CHANGE, GOTO_PREV_CHANGE, GutterManager.goToPrev); KeyBindingManager.addBinding(GOTO_PREV_CHANGE, Preferences.get("gotoPrevChangeShortcut"), brackets.platform); CommandManager.register(Strings.GOTO_NEXT_GIT_CHANGE, GOTO_NEXT_CHANGE, GutterManager.goToNext); KeyBindingManager.addBinding(GOTO_NEXT_CHANGE, Preferences.get("gotoNextChangeShortcut"), brackets.platform); // Init moment - use the correct language moment.lang(brackets.getLocale()); // Show gitPanel when appropriate if (Preferences.get("panelEnabled")) { toggle(true); } } function enable() { EventEmitter.emit(Events.GIT_ENABLED); // this function is called after every Branch.refresh gitPanelMode = null; // $gitPanel.find(".git-available").show(); $gitPanel.find(".git-not-available").hide(); // Main.$icon.removeClass("warning").removeAttr("title"); gitPanelDisabled = false; // after all is enabled refresh(); } function disable(cause) { EventEmitter.emit(Events.GIT_DISABLED, cause); gitPanelMode = cause; // causes: not-repo if (gitPanelMode === "not-repo") { $gitPanel.find(".git-available").hide(); $gitPanel.find(".git-not-available").show(); } else { Main.$icon.addClass("warning").attr("title", cause); toggle(false); gitPanelDisabled = true; } refresh(); } // Event listeners EventEmitter.on(Events.GIT_USERNAME_CHANGED, function (userName) { $gitPanel.find(".git-user-name").text(userName); }); EventEmitter.on(Events.GIT_EMAIL_CHANGED, function (email) { $gitPanel.find(".git-user-email").text(email); }); EventEmitter.on(Events.GIT_REMOTE_AVAILABLE, function () { $gitPanel.find(".git-pull").prop("disabled", false); $gitPanel.find(".git-push").prop("disabled", false); }); EventEmitter.on(Events.GIT_REMOTE_NOT_AVAILABLE, function () { $gitPanel.find(".git-pull").prop("disabled", true); $gitPanel.find(".git-push").prop("disabled", true); }); EventEmitter.on(Events.GIT_ENABLED, function () { // Add info from Git to panel Git.getConfig("user.name").then(function (currentUserName) { EventEmitter.emit(Events.GIT_USERNAME_CHANGED, currentUserName); }); Git.getConfig("user.email").then(function (currentEmail) { EventEmitter.emit(Events.GIT_EMAIL_CHANGED, currentEmail); }); Git.getConfig("gerrit.pushref").then(function (strEnabled) { var enabled = strEnabled === "true"; // Handle the case where we switched to a repo that is using gerrit if (enabled && !Preferences.get("gerritPushref")) { Preferences.persist("gerritPushref", true); } EventEmitter.emit(Events.GERRIT_PUSH_REF_TOGGLED, enabled); }); }); EventEmitter.on(Events.BRACKETS_CURRENT_DOCUMENT_CHANGE, function () { if (!gitPanel) { return; } refreshCurrentFile(); }); EventEmitter.on(Events.BRACKETS_DOCUMENT_SAVED, function () { if (!gitPanel) { return; } refresh(); }); EventEmitter.on(Events.BRACKETS_FILE_CHANGED, function (event, fileSystemEntry) { // files are added or deleted from the directory if (fileSystemEntry.isDirectory) { refresh(); } }); EventEmitter.on(Events.REBASE_MERGE_MODE, function (rebaseEnabled, mergeEnabled) { $gitPanel.find(".git-rebase").toggle(rebaseEnabled); $gitPanel.find(".git-merge").toggle(mergeEnabled); $gitPanel.find("button.git-commit").toggle(!rebaseEnabled && !mergeEnabled); }); EventEmitter.on(Events.HANDLE_GIT_COMMIT, function () { handleGitCommit(lastCommitMessage, false); }); EventEmitter.on(Events.TERMINAL_DISABLE, function () { $gitPanel.find(".git-bash").prop("disabled", true).attr("title", Strings.TERMINAL_DISABLED); }); exports.init = init; exports.refresh = refresh; exports.toggle = toggle; exports.enable = enable; exports.disable = disable; exports.getPanel = function () { return $gitPanel; }; });