mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 07:12:54 +01:00
Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com> Co-Authored-By: Johan <4934465+joolsus@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
58
packages/noodl-git/src/actions/branch.ts
Normal file
58
packages/noodl-git/src/actions/branch.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { getBranches as gitGetBranches } from "../core/for-each-ref";
|
||||
import { Branch } from "../core/models/branch";
|
||||
import { sortBy, groupBy } from "underscore";
|
||||
import { deleteRef } from "../core/update-ref";
|
||||
import { pushDelete } from "../core/push";
|
||||
import { GitActionError, GitActionErrorCode } from "./git-action-error";
|
||||
|
||||
export async function getBranches(
|
||||
repositoryDir: string
|
||||
): Promise<readonly Branch[]> {
|
||||
const branches = await gitGetBranches(repositoryDir);
|
||||
const groupped = groupBy(branches, (x) => x.type);
|
||||
Object.keys(groupped).forEach((key) => sortBy(groupped[key], (x) => x.name));
|
||||
return Object.keys(groupped).flatMap((key) => groupped[key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a local branch, this will leave the remote branch intact if there is one.
|
||||
*
|
||||
* @param repositoryDir
|
||||
* @param branch
|
||||
*/
|
||||
export async function deleteLocalBranch(
|
||||
repositoryDir: string,
|
||||
branch: Branch
|
||||
): Promise<void> {
|
||||
await deleteRef(repositoryDir, branch.ref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a remote branch.
|
||||
*
|
||||
* @param repositoryDir
|
||||
* @param branch
|
||||
*/
|
||||
export async function deleteRemoteBranch(
|
||||
repositoryDir: string,
|
||||
branch: Branch
|
||||
): Promise<void> {
|
||||
if (!branch.upstream) {
|
||||
throw new Error("Branch is not remote.");
|
||||
}
|
||||
|
||||
try {
|
||||
await pushDelete(
|
||||
repositoryDir,
|
||||
branch.upstreamRemoteName,
|
||||
branch.nameWithoutRemote
|
||||
);
|
||||
} catch (error) {
|
||||
const message = error.toString();
|
||||
if (message.includes("remote ref does not exist")) {
|
||||
throw new GitActionError(GitActionErrorCode.BranchNotExisting);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
packages/noodl-git/src/actions/fetch.ts
Normal file
17
packages/noodl-git/src/actions/fetch.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { fetch as gitFetch } from '../core/fetch';
|
||||
import { IGitResult } from '../core/git-error';
|
||||
import { IFetchProgress } from '../core/models/progress';
|
||||
import { IRemote } from '../core/models/remote';
|
||||
import { createErrorFromMessage } from './git-action-error';
|
||||
|
||||
export async function fetch(
|
||||
repositoryDir: string,
|
||||
remote: IRemote,
|
||||
progressCallback?: (progress: IFetchProgress) => void
|
||||
): Promise<IGitResult> {
|
||||
try {
|
||||
return await gitFetch(repositoryDir, remote, progressCallback);
|
||||
} catch (error) {
|
||||
throw createErrorFromMessage(error.toString());
|
||||
}
|
||||
}
|
||||
48
packages/noodl-git/src/actions/git-action-error.ts
Normal file
48
packages/noodl-git/src/actions/git-action-error.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
export enum GitActionErrorCode {
|
||||
LocalRepository,
|
||||
InvalidBranchName,
|
||||
BranchNotExisting,
|
||||
StashNoLocalChanges,
|
||||
AuthorizationFailed
|
||||
}
|
||||
|
||||
/**
|
||||
* GitActionError will be thrown from the action functions,
|
||||
* which are designed to be a higher level of interaction
|
||||
* with git than the other functions.
|
||||
*/
|
||||
export class GitActionError extends Error {
|
||||
public constructor(public readonly code: GitActionErrorCode) {
|
||||
super(getMessage(code));
|
||||
}
|
||||
}
|
||||
|
||||
function getMessage(code: GitActionErrorCode): string {
|
||||
switch (code) {
|
||||
case GitActionErrorCode.LocalRepository:
|
||||
return 'Repository is not published.';
|
||||
|
||||
case GitActionErrorCode.InvalidBranchName:
|
||||
return 'Branch name contains invalid characters.';
|
||||
|
||||
case GitActionErrorCode.BranchNotExisting:
|
||||
return 'Branch does not exist.';
|
||||
|
||||
case GitActionErrorCode.StashNoLocalChanges:
|
||||
return 'No local changes to save.';
|
||||
|
||||
case GitActionErrorCode.AuthorizationFailed:
|
||||
return 'Authorization failed.';
|
||||
|
||||
default:
|
||||
return String(code);
|
||||
}
|
||||
}
|
||||
|
||||
export function createErrorFromMessage(message: string) {
|
||||
if (message.includes('Authentication failed')) {
|
||||
return new GitActionError(GitActionErrorCode.AuthorizationFailed);
|
||||
} else {
|
||||
return new Error(message);
|
||||
}
|
||||
}
|
||||
87
packages/noodl-git/src/actions/history.ts
Normal file
87
packages/noodl-git/src/actions/history.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { getCommits } from "../core/logs";
|
||||
import { Branch } from "../core/models/branch";
|
||||
import { Commit } from "../core/models/snapshot";
|
||||
import { getAheadBehind, revSymmetricDifference } from "../core/rev-list";
|
||||
|
||||
export class CommitHistoryEntry extends Commit {
|
||||
public isLocalAhead: boolean;
|
||||
public isRemoteAhead: boolean;
|
||||
|
||||
public constructor(
|
||||
commit: Commit,
|
||||
isLocalAhead: boolean,
|
||||
isRemoteAhead: boolean
|
||||
) {
|
||||
super(
|
||||
commit.repositoryDir,
|
||||
commit.sha,
|
||||
commit.shortSha,
|
||||
commit.summary,
|
||||
commit.body,
|
||||
commit.author,
|
||||
commit.committer,
|
||||
commit.parentSHAs,
|
||||
commit.tags
|
||||
);
|
||||
|
||||
this.isLocalAhead = isLocalAhead;
|
||||
this.isRemoteAhead = isRemoteAhead;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class designed to handle history between multiple branches.
|
||||
*/
|
||||
export class CommitHistory {
|
||||
public constructor(
|
||||
public readonly repositoryDir: string,
|
||||
public readonly branch: Branch
|
||||
) {}
|
||||
|
||||
public async fetch(count: number): Promise<readonly CommitHistoryEntry[]> {
|
||||
const localGitCommits = await getCommits(
|
||||
this.repositoryDir,
|
||||
undefined,
|
||||
count
|
||||
);
|
||||
|
||||
let commits: CommitHistoryEntry[] = localGitCommits.map(
|
||||
(x) => new CommitHistoryEntry(x, false, false)
|
||||
);
|
||||
|
||||
if (this.branch.remote) {
|
||||
const remoteAheadGitCommits = await getCommits(
|
||||
this.repositoryDir,
|
||||
`${this.branch.nameWithoutRemote}..${this.branch.remote.name}`,
|
||||
count
|
||||
);
|
||||
|
||||
const remoteOnlyCommits = remoteAheadGitCommits.map(
|
||||
(x) => new CommitHistoryEntry(x, false, true)
|
||||
);
|
||||
|
||||
//get commits that aren't pushed
|
||||
const localAheadGitCommits = await getCommits(
|
||||
this.repositoryDir,
|
||||
`${this.branch.remote.name}..${this.branch.nameWithoutRemote}`,
|
||||
count
|
||||
);
|
||||
|
||||
localAheadGitCommits.forEach((aheadCommit) => {
|
||||
const c = commits.find((c) => c.sha === aheadCommit.sha);
|
||||
if (c) {
|
||||
c.isLocalAhead = true;
|
||||
}
|
||||
});
|
||||
|
||||
commits = remoteOnlyCommits.concat(commits);
|
||||
} else {
|
||||
// there is no remote, it's a local branch
|
||||
// flag all the commits as being "ahead" of
|
||||
// the remote (even the commit that was the branching points, and commits before it)
|
||||
commits.forEach((c) => (c.isLocalAhead = true));
|
||||
}
|
||||
|
||||
return commits;
|
||||
}
|
||||
}
|
||||
7
packages/noodl-git/src/actions/index.ts
Normal file
7
packages/noodl-git/src/actions/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from './branch';
|
||||
export * from './history';
|
||||
export * from './git-action-error';
|
||||
export * from './push';
|
||||
export * from './remote';
|
||||
export * from './pull';
|
||||
export * from './fetch';
|
||||
18
packages/noodl-git/src/actions/pull.ts
Normal file
18
packages/noodl-git/src/actions/pull.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Branch } from '../core/models/branch';
|
||||
import { IPullProgress } from '../core/models/progress';
|
||||
import { IRemote } from '../core/models/remote';
|
||||
import { pull as gitPull } from '../core/pull';
|
||||
import { createErrorFromMessage, GitActionError, GitActionErrorCode } from './git-action-error';
|
||||
|
||||
export async function pull(
|
||||
repositoryDir: string,
|
||||
remote: IRemote,
|
||||
branch: Branch | string,
|
||||
progressCallback?: (progress: IPullProgress) => void
|
||||
): Promise<void> {
|
||||
try {
|
||||
return await gitPull(repositoryDir, remote, branch, progressCallback);
|
||||
} catch (error) {
|
||||
throw createErrorFromMessage(error.toString());
|
||||
}
|
||||
}
|
||||
37
packages/noodl-git/src/actions/push.ts
Normal file
37
packages/noodl-git/src/actions/push.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Branch } from '../core/models/branch';
|
||||
import { IPushProgress } from '../core/models/progress';
|
||||
import { push as gitPush } from '../core/push';
|
||||
import { createErrorFromMessage, GitActionError, GitActionErrorCode } from './git-action-error';
|
||||
import { getRemote } from './remote';
|
||||
|
||||
interface PushOptions {
|
||||
baseDir: string;
|
||||
currentBranch: Branch;
|
||||
|
||||
onProgress?: (progress: IPushProgress) => void;
|
||||
}
|
||||
|
||||
export async function push({ baseDir, currentBranch, onProgress }: PushOptions): Promise<boolean> {
|
||||
const remote = await getRemote(baseDir);
|
||||
|
||||
try {
|
||||
return await gitPush(
|
||||
baseDir,
|
||||
remote,
|
||||
currentBranch.nameWithoutRemote,
|
||||
currentBranch.upstreamWithoutRemote,
|
||||
[],
|
||||
undefined,
|
||||
onProgress
|
||||
);
|
||||
} catch (error) {
|
||||
const message = error.toString();
|
||||
if (message.includes('Updates were rejected because the remote contains work that you do')) {
|
||||
throw new Error(
|
||||
'Updates were rejected because there are new changes that you do not have locally. Pull to get the latest changes.'
|
||||
);
|
||||
}
|
||||
|
||||
throw createErrorFromMessage(error.toString());
|
||||
}
|
||||
}
|
||||
30
packages/noodl-git/src/actions/remote.ts
Normal file
30
packages/noodl-git/src/actions/remote.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { IRemote } from '../core/models/remote';
|
||||
import { getRemotes, setRemoteURL as _setRemoteURL } from '../core/remotes';
|
||||
import { GitActionError, GitActionErrorCode } from './git-action-error';
|
||||
|
||||
/**
|
||||
* Returns a single remote.
|
||||
*
|
||||
* @param repositoryDir
|
||||
* @throws {GitHelperError}
|
||||
* @returns A single remote.
|
||||
*/
|
||||
export async function getRemote(repositoryDir: string): Promise<Readonly<IRemote>> {
|
||||
const remotes = await getRemotes(repositoryDir);
|
||||
|
||||
if (remotes.length === 0) {
|
||||
// When there are no remotes, we assume that the repository is local only.
|
||||
// This might not always be the case,
|
||||
// but ideally a remote branch should have been created.
|
||||
throw new GitActionError(GitActionErrorCode.LocalRepository);
|
||||
}
|
||||
|
||||
// TODO: It would be nice if the git client selects a default remote
|
||||
// and then we work from that remote, so you can
|
||||
// technically have many different remotes at once.
|
||||
return remotes[0];
|
||||
}
|
||||
|
||||
export async function setRemoteURL(repositoryDir: string, remoteName: string, url: string): Promise<void> {
|
||||
await _setRemoteURL(repositoryDir, remoteName, url);
|
||||
}
|
||||
Reference in New Issue
Block a user