Skip to content

Commit

Permalink
feat(git): Implement git checkout
Browse files Browse the repository at this point in the history
For now this only lets you check out branches, not files.
  • Loading branch information
AtkinsSJ authored and KernelDeimos committed Jun 28, 2024
1 parent 98a4b9e commit 35e4453
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/git/src/subcommands/__exports__.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
// Generated by /tools/gen.js
import module_add from './add.js'
import module_branch from './branch.js'
import module_checkout from './checkout.js'
import module_clone from './clone.js'
import module_commit from './commit.js'
import module_config from './config.js'
Expand All @@ -34,6 +35,7 @@ import module_version from './version.js'
export default {
"add": module_add,
"branch": module_branch,
"checkout": module_checkout,
"clone": module_clone,
"commit": module_commit,
"config": module_config,
Expand Down
155 changes: 155 additions & 0 deletions packages/git/src/subcommands/checkout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright (C) 2024 Puter Technologies Inc.
*
* This file is part of Puter's Git client.
*
* Puter's Git client is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import git from 'isomorphic-git';
import { find_repo_root } from '../git-helpers.js';
import { SHOW_USAGE } from '../help.js';

const CHECKOUT = {
name: 'checkout',
usage: [
'git checkout [--force] <branch>',
'git checkout (-b | -B) [--force] <new-branch> [<start-point>]',
],
description: `Switch branches.`,
args: {
allowPositionals: true,
tokens: true,
strict: false,
options: {
'new-branch': {
description: 'Create a new branch and then check it out.',
type: 'boolean',
short: 'b',
default: false,
},
'force': {
description: 'Perform the checkout forcefully. For --new-branch, ignores whether the branch already exists. For checking out branches, ignores and overwrites any unstaged changes.',
type: 'boolean',
short: 'f',
},
},
},
execute: async (ctx) => {
const { io, fs, env, args } = ctx;
const { stdout, stderr } = io;
const { options, positionals, tokens } = args;
const cache = {};

for (const token of tokens) {
if (token.kind !== 'option') continue;

if (token.name === 'B') {
options['new-branch'] = true;
options.force = true;
delete options['B'];
continue;
}

// Report any options that we don't recognize
let option_recognized = false;
for (const [key, value] of Object.entries(CHECKOUT.args.options)) {
if (key === token.name || value.short === token.name) {
option_recognized = true;
break;
}
}
if (!option_recognized) {
stderr(`Unrecognized option: ${token.rawName}`);
throw SHOW_USAGE;
}
}

const { repository_dir, git_dir } = await find_repo_root(fs, env.PWD);

// DRY: Copied from branch.js
const get_current_branch = async () => git.currentBranch({
fs,
dir: repository_dir,
gitdir: git_dir,
test: true,
});
const get_all_branches = async () => git.listBranches({
fs,
dir: repository_dir,
gitdir: git_dir,
});
const get_branch_data = async () => {
const [branches, current_branch] = await Promise.all([
get_all_branches(),
get_current_branch(),
]);
return { branches, current_branch };
}

if (options['new-branch']) {
const { branches, current_branch } = await get_branch_data();
if (positionals.length === 0 || positionals.length > 2) {
stderr('error: Expected 1 or 2 arguments, for <new-branch> [<start-point>].');
throw SHOW_USAGE;
}
const branch_name = positionals.shift();
const starting_point = positionals.shift() ?? current_branch;

if (branches.includes(branch_name) && !options.force)
throw new Error(`A branch named '${branch_name}' already exists.`);

await git.branch({
fs,
dir: repository_dir,
gitdir: git_dir,
ref: branch_name,
object: starting_point,
checkout: true,
force: options.force,
});
stdout(`Switched to a new branch '${branch_name}'`);
return;
}

// Check out a branch
// TODO: Check out files.
{
if (positionals.length === 0 || positionals.length > 1) {
stderr('error: Expected 1 argument, for <branch>.');
throw SHOW_USAGE;
}
const { branches, current_branch } = await get_branch_data();
const branch_name = positionals.shift();

if (branch_name === current_branch) {
stdout(`Already on '${branch_name}'`);
return;
}

if (!branches.includes(branch_name))
throw new Error(`Branch '${branch_name}' not found.`);

await git.checkout({
fs,
dir: repository_dir,
gitdir: git_dir,
cache,
ref: branch_name,
force: options.force,
});
stdout(`Switched to branch '${branch_name}'`);
}
}
};
export default CHECKOUT;

0 comments on commit 35e4453

Please sign in to comment.