Skip to content

Commit

Permalink
feat: branch management
Browse files Browse the repository at this point in the history
close #132
close #220
  • Loading branch information
Vinzent03 committed Sep 20, 2022
1 parent 481d8a4 commit caaacd1
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 23 deletions.
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const DEFAULT_SETTINGS: ObsidianGitSettings = {
username: "",
showedMobileNotice: false,
refreshSourceControlTimer: 7000,
showBranchStatusBar: true
};

export const GIT_VIEW_CONFIG = {
Expand Down
6 changes: 6 additions & 0 deletions src/gitManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ export abstract class GitManager {

abstract checkout(branch: string): Promise<void>;

abstract createBranch(branch: string): Promise<void>;

abstract deleteBranch(branch: string, force: boolean): Promise<void>;

abstract branchIsMerged(branch: string): Promise<boolean>;

abstract init(): Promise<void>;

abstract clone(url: string, dir: string): Promise<void>;
Expand Down
24 changes: 23 additions & 1 deletion src/isomorphicGit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class IsomorphicGit extends GitManager {
onAuth: () => {
return {
username: this.plugin.settings.username,
password: this.plugin.localStorage.getPassword()
password: this.plugin.localStorage.getPassword() ?? undefined
};
},
onAuthFailure: async () => {
Expand Down Expand Up @@ -400,6 +400,28 @@ export class IsomorphicGit extends GitManager {
}
}

async createBranch(branch: string): Promise<void> {
try {
await this.wrapFS(git.branch({ ...this.getRepo(), ref: branch, checkout: true }));
} catch (error) {
this.plugin.displayError(error);
throw error;
}
}

async deleteBranch(branch: string): Promise<void> {
try {
await this.wrapFS(git.deleteBranch({ ...this.getRepo(), ref: branch, }));
} catch (error) {
this.plugin.displayError(error);
throw error;
}
}

async branchIsMerged(branch: string): Promise<boolean> {
return true;
}

async init(): Promise<void> {
try {
await this.wrapFS(git.init(this.getRepo()));
Expand Down
92 changes: 90 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ import { openHistoryInGitHub, openLineInGitHub } from "./openInGitHub";
import { SimpleGit } from "./simpleGit";
import { FileStatusResult, ObsidianGitSettings, PluginState, Status, UnstagedFile } from "./types";
import DiffView from "./ui/diff/diffView";
import { BranchModal } from "./ui/modals/branchModal";
import { GeneralModal } from "./ui/modals/generalModal";
import { IgnoreModal } from "./ui/modals/ignoreModal";
import GitView from "./ui/sidebar/sidebarView";
import { BranchStatusBar } from "./ui/statusBar/branchStatusBar";
import { getNewLeaf } from "./utils";

export default class ObsidianGit extends Plugin {
gitManager: GitManager;
localStorage: LocalStorageSettings;
settings: ObsidianGitSettings;
statusBar: StatusBar;
statusBar?: StatusBar;
branchBar?: BranchStatusBar;
state: PluginState;
timeoutIDBackup?: number;
timeoutIDPush?: number;
Expand Down Expand Up @@ -319,6 +322,30 @@ export default class ObsidianGit extends Plugin {
}
});

this.addCommand({
id: "switch-branch",
name: "Switch branch",
callback: () => {
this.switchBranch();
}
});

this.addCommand({
id: "create-branch",
name: "Create new branch",
callback: () => {
this.createBranch();
}
});

this.addCommand({
id: "delete-branch",
name: "Delete branch",
callback: () => {
this.deleteBranch();
}
});

this.registerEvent(
this.app.workspace.on('file-menu', (menu, file, source) => {
this.handleFileMenu(menu, file, source);
Expand All @@ -330,9 +357,19 @@ export default class ObsidianGit extends Plugin {
const statusBarEl = this.addStatusBarItem();
this.statusBar = new StatusBar(statusBarEl, this);
this.registerInterval(
window.setInterval(() => this.statusBar.display(), 1000)
window.setInterval(() => this.statusBar?.display(), 1000)
);
}


if (Platform.isDesktop && this.settings.showBranchStatusBar) {
const branchStatusBarEl = this.addStatusBarItem();
this.branchBar = new BranchStatusBar(branchStatusBarEl, this);
this.registerInterval(
window.setInterval(() => this.branchBar?.display(), 60000)
);
}

this.app.workspace.onLayoutReady(() => this.init());

}
Expand Down Expand Up @@ -519,6 +556,8 @@ export default class ObsidianGit extends Plugin {
this.registerEvent(this.createEvent);
this.registerEvent(this.renameEvent);

this.branchBar?.display();

dispatchEvent(new CustomEvent('git-refresh'));

if (this.settings.autoPullOnBoot) {
Expand Down Expand Up @@ -851,6 +890,55 @@ export default class ObsidianGit extends Plugin {
return true;
}

async switchBranch(): Promise<string | undefined> {
if (!await this.isAllInitialized()) return;

const branchInfo = await this.gitManager.branchInfo();
const selectedBranch = await new BranchModal(branchInfo.branches).open();

if (selectedBranch != undefined) {
await this.gitManager.checkout(selectedBranch);
this.displayMessage(`Switched to ${selectedBranch}`);
this.branchBar?.display();
return selectedBranch;
}
}

async createBranch(): Promise<string | undefined> {
if (!await this.isAllInitialized()) return;

const newBranch = await new GeneralModal(app, [], "Create new branch", false).open();
if (newBranch != undefined) {
await this.gitManager.createBranch(newBranch);
this.displayMessage(`Created new branch ${newBranch}`);
this.branchBar?.display();
return newBranch;
}
}

async deleteBranch(): Promise<string | undefined> {
if (!await this.isAllInitialized()) return;

const branchInfo = await this.gitManager.branchInfo();
if (branchInfo.current)
branchInfo.branches.remove(branchInfo.current);
const branch = await new GeneralModal(app, branchInfo.branches, "Delete branch", false, true).open();
if (branch != undefined) {
let force = false;
if (!await this.gitManager.branchIsMerged(branch)) {
const forceAnswer = await new GeneralModal(app, ["YES", "NO"], "This branch isn't merged into HEAD. Force delete?", false, true).open();
if (forceAnswer !== "YES") {
return;
}
force = forceAnswer === "YES";
}
await this.gitManager.deleteBranch(branch, force);
this.displayMessage(`Deleted branch ${branch}`);
this.branchBar?.display();
return branch;
}
}

async remotesAreSet(): Promise<boolean> {
if (!(await this.gitManager.branchInfo()).tracking) {
new Notice("No upstream branch is set. Please select one.");
Expand Down
33 changes: 14 additions & 19 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export class ObsidianGitSettingsTab extends PluginSettingTab {
.setDesc('Specify custom hostname for every device.')
.addText((text) =>
text
.setValue(plugin.localStorage.getHostname())
.setValue(plugin.localStorage.getHostname() ?? "")
.onChange(async (value) => {
plugin.localStorage.setHostname(value);
})
Expand Down Expand Up @@ -302,23 +302,6 @@ export class ObsidianGitSettingsTab extends PluginSettingTab {
containerEl.createEl("br");
containerEl.createEl("h3", { text: "Miscellaneous" });

if (gitReady)
new Setting(containerEl)
.setName("Current branch")
.setDesc("Switch to a different branch")
.addDropdown(async (dropdown) => {
const branchInfo = await plugin.gitManager.branchInfo();
for (const branch of branchInfo.branches) {
dropdown.addOption(branch, branch);
}
dropdown.setValue(branchInfo.current);
dropdown.onChange(async (option) => {
await plugin.gitManager.checkout(option);
new Notice(`Checked out to ${option}`);
});
});


new Setting(containerEl)
.setName("Automatically refresh Source Control View on file changes")
.setDesc("On slower machines this may cause lags. If so, just disable this option")
Expand Down Expand Up @@ -371,6 +354,18 @@ export class ObsidianGitSettingsTab extends PluginSettingTab {
})
);

new Setting(containerEl)
.setName("Show branch status bar")
.setDesc("Obsidian must be restarted for the changes to take affect")
.addToggle((toggle) =>
toggle
.setValue(plugin.settings.showBranchStatusBar)
.onChange((value) => {
plugin.settings.showBranchStatusBar = value;
plugin.saveSettings();
})
);

new Setting(containerEl)
.setName("Show changes files count in status bar")
.addToggle((toggle) =>
Expand Down Expand Up @@ -402,7 +397,7 @@ export class ObsidianGitSettingsTab extends PluginSettingTab {
new Setting(containerEl)
.setName("Custom Git binary path")
.addText((cb) => {
cb.setValue(plugin.localStorage.getGitPath());
cb.setValue(plugin.localStorage.getGitPath() ?? "");
cb.setPlaceholder("git");
cb.onChange((value) => {
plugin.localStorage.setGitPath(value);
Expand Down
13 changes: 13 additions & 0 deletions src/simpleGit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,19 @@ export class SimpleGit extends GitManager {
await this.git.checkout(branch, (err) => this.onError(err));
}

async createBranch(branch: string): Promise<void> {
await this.git.checkout(["-b", branch], (err) => this.onError(err));
}

async deleteBranch(branch: string, force: boolean): Promise<void> {
await this.git.branch([force ? "-D" : "-d", branch], (err) => this.onError(err));
}

async branchIsMerged(branch: string): Promise<boolean> {
const notMergedBranches = await this.git.branch(["--no-merged"], (err) => this.onError(err));
return !notMergedBranches.all.contains(branch);
}

async init(): Promise<void> {
await this.git.init(false, (err) => this.onError(err));
}
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface ObsidianGitSettings {
basePath: string;
showedMobileNotice: boolean;
refreshSourceControlTimer: number;
showBranchStatusBar: boolean;
}

export type SyncMethod = 'rebase' | 'merge' | 'reset';
Expand Down
35 changes: 35 additions & 0 deletions src/ui/modals/branchModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { FuzzySuggestModal } from "obsidian";

export class BranchModal extends FuzzySuggestModal<string> {
resolve: ((value: string | undefined | PromiseLike<string | undefined>) => void);


constructor(private readonly branches: string[]) {
super(app);
this.setPlaceholder("Select branch to checkout");
}

getItems(): string[] {
return this.branches;
}
getItemText(item: string): string {
return item;
}
onChooseItem(item: string, evt: MouseEvent | KeyboardEvent): void {
this.resolve(item);
}

open(): Promise<string> {
super.open();
return new Promise((resolve) => {
this.resolve = resolve;
});
}

async onClose() {
//onClose gets called before onChooseItem
await new Promise(resolve => setTimeout(resolve, 10));
if (this.resolve) this.resolve(undefined);
}

}
2 changes: 1 addition & 1 deletion src/ui/modals/generalModal.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { App, SuggestModal } from "obsidian";

export class GeneralModal extends SuggestModal<string> {
resolve: ((value: string | PromiseLike<string>) => void) | null = null;
resolve: ((value: string | undefined | PromiseLike<string | undefined>) => void);


constructor(app: App, private options: string[], placeholder: string, private allowEmpty = false, private onlySelection: boolean = false) {
Expand Down
23 changes: 23 additions & 0 deletions src/ui/statusBar/branchStatusBar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import ObsidianGit from "src/main";

export class BranchStatusBar {
constructor(private statusBarEl: HTMLElement, private readonly plugin: ObsidianGit) {
this.statusBarEl.addClass("mod-clickable");
this.statusBarEl.onClickEvent((e) => {
this.plugin.switchBranch();
});
}

async display() {
if (this.plugin.gitReady) {
const branchInfo = await this.plugin.gitManager.branchInfo();
if (branchInfo.current != undefined) {
this.statusBarEl.setText(branchInfo.current);
} else {
this.statusBarEl.empty();
}
} else {
this.statusBarEl.empty();
}
}
}

0 comments on commit caaacd1

Please sign in to comment.