Skip to content

Commit 95c8235

Browse files
AtkinsSJKernelDeimos
authored andcommitted
feat(git): Implement git clone
1 parent fa81dca commit 95c8235

File tree

3 files changed

+123
-0
lines changed

3 files changed

+123
-0
lines changed

packages/git/src/git-helpers.js

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
*/
1919
import path from 'path-browserify';
2020

21+
export const PROXY_URL = 'https://cors.isomorphic-git.org';
22+
2123
/**
2224
* Attempt to locate the git repository directory.
2325
* @throws Error If no git repository could be found, or another error occurred.

packages/git/src/subcommands/__exports__.js

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
// Generated by /tools/gen.js
2020
import module_add from './add.js'
21+
import module_clone from './clone.js'
2122
import module_commit from './commit.js'
2223
import module_config from './config.js'
2324
import module_help from './help.js'
@@ -29,6 +30,7 @@ import module_version from './version.js'
2930

3031
export default {
3132
"add": module_add,
33+
"clone": module_clone,
3234
"commit": module_commit,
3335
"config": module_config,
3436
"help": module_help,

packages/git/src/subcommands/clone.js

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright (C) 2024 Puter Technologies Inc.
3+
*
4+
* This file is part of Puter's Git client.
5+
*
6+
* Puter's Git client is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published
8+
* by the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
import git from 'isomorphic-git';
20+
import http from 'isomorphic-git/http/web';
21+
import { PROXY_URL } from '../git-helpers.js';
22+
import { SHOW_USAGE } from '../help.js';
23+
import path from 'path-browserify';
24+
25+
export default {
26+
name: 'clone',
27+
usage: 'git clone <repository> [<directory>]',
28+
description: 'Clone a repository into a new directory.',
29+
args: {
30+
allowPositionals: true,
31+
options: {
32+
depth: {
33+
description: 'Only clone the specified number of commits. Implies --single-branch unless --no-single-branch is given.',
34+
type: 'string',
35+
},
36+
'single-branch': {
37+
description: 'Only clone the history of the primary branch',
38+
type: 'boolean',
39+
default: false,
40+
},
41+
'no-single-branch': {
42+
description: 'Clone all history (default)',
43+
type: 'boolean',
44+
},
45+
'no-tags': {
46+
description: 'Do not clone any tags from the remote',
47+
type: 'boolean',
48+
default: false,
49+
},
50+
},
51+
},
52+
execute: async (ctx) => {
53+
const { io, fs, env, args } = ctx;
54+
const { stdout, stderr } = io;
55+
const { options, positionals } = args;
56+
57+
if (options.depth) {
58+
const depth = Number.parseInt(options.depth);
59+
if (!depth) {
60+
stderr('Invalid --depth: Must be an integer greater than 0.');
61+
return 1;
62+
}
63+
options.depth = depth;
64+
options['single-branch'] = true;
65+
}
66+
67+
if (options['no-single-branch']) {
68+
options['single-branch'] = false;
69+
delete options['no-single-branch'];
70+
}
71+
72+
const [repository, directory] = positionals;
73+
if (!repository) {
74+
stderr('fatal: You must specify a repository to clone.');
75+
throw SHOW_USAGE;
76+
}
77+
78+
let repo_path;
79+
if (directory) {
80+
repo_path = path.resolve(env.PWD, directory);
81+
} else {
82+
// Try to extract directory from the repository url
83+
let repo_name = repository.slice(repository.lastIndexOf('/') + 1);
84+
if (repo_name.endsWith('.git')) {
85+
repo_name = repo_name.slice(0, -4);
86+
}
87+
88+
repo_path = path.resolve(env.PWD, repo_name);
89+
}
90+
91+
// The path must either not exist, or be a directory that is empty
92+
try {
93+
const readdir = await fs.promises.readdir(repo_path);
94+
if (readdir.length !== 0) {
95+
stderr(`fatal: ${repo_path} is not empty.`);
96+
return 1;
97+
}
98+
} catch (e) {
99+
if (e.code !== 'ENOENT') {
100+
stderr(`fatal: ${repo_path} is a file.`);
101+
return 1;
102+
}
103+
}
104+
105+
stdout(`Cloning into '${path.relative(env.PWD, repo_path)}'...`);
106+
107+
await git.clone({
108+
fs,
109+
http,
110+
corsProxy: PROXY_URL,
111+
dir: repo_path,
112+
url: repository,
113+
depth: options.depth,
114+
singleBranch: options['single-branch'],
115+
noTags: options['no-tags'],
116+
onMessage: (message) => { stdout(message); },
117+
});
118+
}
119+
}

0 commit comments

Comments
 (0)