Skip to content

Commit

Permalink
Merge branch 'main' into all-contributors/add-paul-dingemans
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisCarini authored Jan 21, 2025
2 parents ea12c1f + 173d586 commit 072255a
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.util.Set;
import java.util.stream.Collectors;

import com.intellij.openapi.vcs.FilePath;
import git4idea.index.GitFileStatus;
import org.jetbrains.annotations.NotNull;

import com.chriscarini.jetbrains.gitpushreminder.settings.SettingsManager;
Expand Down Expand Up @@ -37,6 +39,20 @@ static List<RepositoryAndBranch> getBranchesWithUnpushedCommits(
.collect(Collectors.toList());
}

static List<FilePath> getUntrackedFiles(@NotNull final Project project) {
return GitRepositoryManager.getInstance(project)
.getRepositories().stream()
.flatMap(gitRepository -> gitRepository.getUntrackedFilesHolder().getUntrackedFilePaths().stream())
.collect(Collectors.toList());
}

static List<GitFileStatus> getFilesWithUncommittedChanges(@NotNull final Project project) {
return GitRepositoryManager.getInstance(project)
.getRepositories().stream()
.flatMap(gitRepository -> gitRepository.getStagingAreaHolder().getAllRecords().stream())
.collect(Collectors.toList());
}

@NotNull
private static Set<RepositoryAndBranch> checkAllBranches(
@NotNull final Project project,
Expand Down Expand Up @@ -76,7 +92,7 @@ private static Boolean isLocalBranchWithUnpushedCommits(
) {
final GitRemoteBranch trackedBranch = currentBranch.findTrackedBranch(gitRepository);
if (trackedBranch == null) {
return !SettingsManager.getInstance().getState().countUntrackedBranchAsPushed;
return !SettingsManager.getInstance().getState().allowUntrackedBranches;
}

final String currentBranchName = currentBranch.getName();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package com.chriscarini.jetbrains.gitpushreminder;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.intellij.ide.SaveAndSyncHandler;
import com.intellij.openapi.progress.util.BackgroundTaskUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.changes.VcsManagedFilesHolder;
import com.intellij.util.Time;
import git4idea.index.GitFileStatus;
import git4idea.repo.GitRepositoryManager;
import git4idea.status.GitStagingAreaHolder;
import org.jetbrains.annotations.NotNull;

import com.chriscarini.jetbrains.gitpushreminder.messages.PluginMessages;
Expand All @@ -13,41 +23,135 @@
import com.intellij.openapi.ui.Messages;

public class GitPushReminder implements ProjectCloseHandler {
private static final int TIME_BETWEEN = 2 * Time.SECOND;
private long lastRanTime = System.currentTimeMillis() - TIME_BETWEEN - 1;
private final List<String> body = new ArrayList<>();

/**
* We can close the project if/when all the following criteria are met:
* <p>
* 0. If "Show dialog?" IS UNCHECKED --> TRUE
* 1. "Allow uncommitted changes" is UNCHECKED &&& There are uncommitted changes --> Show Dialog
* 2. "Allow untracked branches" is UNCHECKED &&& There are untracked branches --> Show Dialog
* 3. "Allow untracked files" is UNCHECKED &&& There are untracked files --> Show Dialog
* 4. Otherwise, Show Dialog (if needed) and do what the user wants, else TRUE
*
* @param project project to check
* @return true if the project can be closed, false otherwise
*/
@Override
public boolean canClose(@NotNull Project project) {
final List<GitHelper.RepositoryAndBranch> branchesWithUnpushedCommits = GitHelper.getBranchesWithUnpushedCommits(project, SettingsManager.getInstance().getState().checkAllBranches);

// If there are *NO* branches with outgoing/un-pushed commits, then we can close.
boolean canClose = branchesWithUnpushedCommits.isEmpty();

if (!canClose && SettingsManager.getInstance().getState().showDialog) {
final int dialogResult = Messages.showOkCancelDialog(project,
PluginMessages.get("git.push.reminder.closing.dialog.body.unpushed.branches",
branchesWithUnpushedCommits.stream()
.sorted((o1, o2) -> {
if (o1.repository().getRoot().getName().equals(o2.repository().getRoot().getName())) {
return o1.branch().getName().compareTo(o2.branch().getName());
}
return o1.repository().getRoot().getName().compareTo(o2.repository().getRoot().getName());
})
.map(repoAndBranch -> String.format(
"%s (%s)", //NON-NLS
repoAndBranch.branch().getName(),
repoAndBranch.repository().getRoot().getName()
)
)
.collect(Collectors.joining("</li><li>"))
),
PluginMessages.get("git.push.reminder.closing.dialog.title", branchesWithUnpushedCommits.size()),
PluginMessages.get("git.push.reminder.closing.dialog.button.close.anyway"),
PluginMessages.get("git.push.reminder.closing.dialog.button.keep.project.open"),
Messages.getWarningIcon()
// Sometimes upon program exit (not project close), two dialogs are shown.
// `lastRanTime` is a mechanism to prevent that from happening.
if (System.currentTimeMillis() - lastRanTime < TIME_BETWEEN) {
return true;
}

/*
* - Show dialog?
* - If checked, a dialog will be shown if there are any un-pushed branches, untracked files, or uncommitted changes.
* - If unchecked, no dialog box will be shown and the project will be allowed to close without notice.
*
* 0. If "Show dialog?" IS UNCHECKED --> TRUE
*/
if (!SettingsManager.getInstance().getState().showDialog) {
return true;
}

// Clear any previous body of the dialog on each run
body.clear();

// Try to update the state of repos
SaveAndSyncHandler.getInstance().refreshOpenFiles();
BackgroundTaskUtil.syncPublisher(project, VcsManagedFilesHolder.TOPIC).updatingModeChanged();
GitRepositoryManager.getInstance(project).getRepositories().forEach(gitRepository -> {
BackgroundTaskUtil.syncPublisher(project, GitStagingAreaHolder.TOPIC).stagingAreaChanged(gitRepository);
});

/*
* - Allow uncommitted changes
* - If checked, uncommited changes in changelists will be 'allowed'.
* - If unchecked, a dialog will be shown warning that uncommitted changes will need to be committed.
*
* 1. If "Allow uncommitted changes" is UNCHECKED &&& There are uncommitted changes --> Show Dialog
*/
if (!SettingsManager.getInstance().getState().allowUncommittedChanges) {
final List<GitFileStatus> untrackedCommittedFiles = GitHelper.getFilesWithUncommittedChanges(project);
addToDialogBody(
untrackedCommittedFiles,
PluginMessages.get("git.push.reminder.closing.dialog.body.uncommitted.changes.title"),
(gitFileStatus) -> gitFileStatus.getPath().getName()
);
}

return dialogResult == MessageConstants.OK;
/*
* - Allow untracked branches
* - If checked, local branches with no tracked remote branch will be 'allowed'.
* - If unchecked, a dialog will be shown warning that local branches will need to have a tracked remote branch.
*
* 2. If "Allow untracked branches" is UNCHECKED &&& There are untracked branches --> Show Dialog
*/
if (!SettingsManager.getInstance().getState().allowUntrackedBranches) {
final List<GitHelper.RepositoryAndBranch> untrackedBranches = GitHelper.getBranchesWithUnpushedCommits(project, SettingsManager.getInstance().getState().checkAllBranches);
addToDialogBody(
untrackedBranches,
PluginMessages.get("git.push.reminder.closing.dialog.body.untracked.branches.title"),
repoAndBranch -> String.format("%s (%s)", repoAndBranch.branch().getName(), repoAndBranch.repository().getRoot().getName()) //NON-NLS
);
}

return true;
/*
* - Allow untracked files
* - If checked, untracked files which are not added to a changelist will be 'allowed'.
* - If unchecked, a dialog will be shown warning that untracked files will need to become tracked.
*
* 3. If "Allow untracked files" is UNCHECKED &&& There are untracked files --> Show Dialog
*/
if (!SettingsManager.getInstance().getState().allowUntrackedFiles) {
final List<FilePath> untrackedFiles = GitHelper.getUntrackedFiles(project);
addToDialogBody(
untrackedFiles,
PluginMessages.get("git.push.reminder.closing.dialog.body.untracked.files.title"),
FilePath::getName
);
}

/*
* 4. Otherwise,
* - if the user opted to "Keep Project Open" in the dialog -> FALSE
* - Otherwise, TRUE
*/
boolean okToClose = body.isEmpty() || doesUserWantProjectClosed(project, body);

// Note: `lastRanTime` needs to be updated *AFTER* the dialog is shown.
lastRanTime = System.currentTimeMillis();

return okToClose;
}

private <T> void addToDialogBody(
final List<T> checkList,
final String title,
final Function<T, String> mapper
) {
if (!checkList.isEmpty()) {
final String listItems = checkList.stream()
.map(mapper)
.sorted()
.collect(Collectors.joining("</li><li>"));
this.body.add(PluginMessages.get("git.push.reminder.closing.dialog.body.list.template", title, listItems));
}
}

private boolean doesUserWantProjectClosed(@NotNull final Project project, @NotNull final List<String> body) {
final int dialogResult = Messages.showOkCancelDialog(project,
PluginMessages.get("git.push.reminder.closing.dialog.body.template", String.join("", body)),
PluginMessages.get("git.push.reminder.closing.dialog.title"),
PluginMessages.get("git.push.reminder.closing.dialog.button.close.anyway"),
PluginMessages.get("git.push.reminder.closing.dialog.button.keep.project.open"),
Messages.getWarningIcon()
);

return dialogResult == MessageConstants.OK;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ public class SettingsConfigurable implements Configurable {
private final JPanel mainPanel = new JBPanel<>();

private final JBCheckBox checkAllBranchesField = new JBCheckBox();
private final JBCheckBox countUntrackedBranchAsPushedField = new JBCheckBox();
private final JBCheckBox allowUncommittedChangesField = new JBCheckBox();
private final JBCheckBox allowUntrackedBranchField = new JBCheckBox();
private final JBCheckBox allowUntrackedFilesField = new JBCheckBox();
private final JBCheckBox showDialogField = new JBCheckBox();
private final JBCheckBox showSwitchDialogField = new JBCheckBox();

Expand All @@ -44,9 +46,15 @@ private void buildMainPanel() {
.addLabeledComponent(PluginMessages.get("git.push.reminder.settings.check.all.branches.label"), checkAllBranchesField)
.addTooltip(PluginMessages.get("git.push.reminder.settings.check.all.branches.tooltip"))
.addSeparator()
.addLabeledComponent(PluginMessages.get("git.push.reminder.settings.allow.untracked.branches.label"), countUntrackedBranchAsPushedField)
.addLabeledComponent(PluginMessages.get("git.push.reminder.settings.allow.uncommitted.changes.label"), allowUncommittedChangesField)
.addTooltip(PluginMessages.get("git.push.reminder.settings.allow.uncommitted.changes.tooltip"))
.addSeparator()
.addLabeledComponent(PluginMessages.get("git.push.reminder.settings.allow.untracked.branches.label"), allowUntrackedBranchField)
.addTooltip(PluginMessages.get("git.push.reminder.settings.allow.untracked.branches.tooltip"))
.addSeparator()
.addLabeledComponent(PluginMessages.get("git.push.reminder.settings.allow.untracked.files.label"), allowUntrackedFilesField)
.addTooltip(PluginMessages.get("git.push.reminder.settings.allow.untracked.files.tooltip"))
.addSeparator()
.addLabeledComponent(PluginMessages.get("git.push.reminder.settings.show.dialog.label"), showDialogField)
.addTooltip(PluginMessages.get("git.push.reminder.settings.show.dialog.tooltip"))
.addSeparator()
Expand Down Expand Up @@ -86,7 +94,9 @@ private SettingsManager.GitPushReminderSettingsState getSettingsFromUserInput()
final SettingsManager.GitPushReminderSettingsState settingsState = new SettingsManager.GitPushReminderSettingsState();

settingsState.checkAllBranches = checkAllBranchesField.isSelected();
settingsState.countUntrackedBranchAsPushed = countUntrackedBranchAsPushedField.isSelected();
settingsState.allowUncommittedChanges = allowUncommittedChangesField.isSelected();
settingsState.allowUntrackedBranches = allowUntrackedBranchField.isSelected();
settingsState.allowUntrackedFiles = allowUntrackedFilesField.isSelected();
settingsState.showDialog = showDialogField.isSelected();
settingsState.showSwitchDialog = showSwitchDialogField.isSelected();

Expand All @@ -111,7 +121,9 @@ private void updateUserInputFields(@Nullable final SettingsManager.GitPushRemind
}

checkAllBranchesField.setSelected(settings.checkAllBranches);
countUntrackedBranchAsPushedField.setSelected(settings.countUntrackedBranchAsPushed);
allowUncommittedChangesField.setSelected(settings.allowUncommittedChanges);
allowUntrackedBranchField.setSelected(settings.allowUntrackedBranches);
allowUntrackedFilesField.setSelected(settings.allowUntrackedFiles);
showDialogField.setSelected(settings.showDialog);
showSwitchDialogField.setSelected(settings.showSwitchDialog);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,18 @@ public void loadState(@NotNull final GitPushReminderSettingsState gitPushReminde

public static class GitPushReminderSettingsState {

public boolean allowUncommittedChanges;
public boolean allowUntrackedBranches;
public boolean allowUntrackedFiles;
public boolean checkAllBranches;
public boolean countUntrackedBranchAsPushed;
public boolean showDialog;
public boolean showSwitchDialog;

public GitPushReminderSettingsState() {
this.allowUncommittedChanges = false;
this.allowUntrackedBranches = false;
this.allowUntrackedFiles = false;
this.checkAllBranches = false;
this.countUntrackedBranchAsPushed = false;
this.showDialog = true;
this.showSwitchDialog = false;
}
Expand All @@ -54,15 +58,24 @@ public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GitPushReminderSettingsState that = (GitPushReminderSettingsState) o;
return checkAllBranches == that.checkAllBranches &&
countUntrackedBranchAsPushed == that.countUntrackedBranchAsPushed &&
return allowUncommittedChanges == that.allowUncommittedChanges &&
allowUntrackedBranches == that.allowUntrackedBranches &&
allowUntrackedFiles == that.allowUntrackedFiles &&
checkAllBranches == that.checkAllBranches &&
showDialog == that.showDialog &&
showSwitchDialog == that.showSwitchDialog;
}

@Override
public int hashCode() {
return Objects.hash(checkAllBranches, countUntrackedBranchAsPushed, showDialog, showSwitchDialog);
return Objects.hash(
allowUncommittedChanges,
allowUntrackedBranches,
allowUntrackedFiles,
checkAllBranches,
showDialog,
showSwitchDialog
);
}
}
}
Loading

0 comments on commit 072255a

Please sign in to comment.