ggdgsdbsdbbb / git / git.v
133 lines · 122 sloc · 2.98 KB · a4967cf53207c5a11a9bd4031b22e2bfe1e3a479
Raw
1module git
2
3import os
4import time
5
6pub struct Git {}
7
8pub fn Git.exec(args []string) os.Result {
9 mut git_args := ['git']
10 git_args << args
11 return os.exec(git_args)
12}
13
14pub fn Git.exec_in_dir(dir string, args []string) os.Result {
15 mut git_args := ['-C', dir]
16 git_args << args
17 return Git.exec(git_args)
18}
19
20pub fn Git.exec_in_dir_command(dir string, command string) os.Result {
21 return Git.exec_in_dir(dir, split_command(command))
22}
23
24pub fn Git.exec_shell(command string) os.Result {
25 return os.exec(['/bin/sh', '-c', command])
26}
27
28pub fn Git.clone(url string, path string) os.Result {
29 println('new clone url="${url}" path="${path}"')
30 return os.exec(['git', 'clone', '--bare', url, path])
31}
32
33// Git.clone_with_progress runs `git clone --bare --progress` and streams
34// every byte of git's progress output (which goes to stderr) into
35// `progress_path` while the clone is running, so a separate process can
36// poll the file and show live progress to the user.
37pub fn Git.clone_with_progress(url string, path string, progress_path string) os.Result {
38 println('new clone (progress) url="${url}" path="${path}" progress="${progress_path}"')
39 os.rm(progress_path) or {}
40 mut p := os.new_process('git')
41 p.set_args(['clone', '--bare', '--progress', url, path])
42 p.set_redirect_stdio()
43 p.run()
44 mut log := os.open_append(progress_path) or {
45 eprintln('clone_with_progress: cannot open progress file "${progress_path}": ${err}')
46 // fall back to non-streaming behaviour
47 p.wait()
48 out := p.stdout_slurp() + p.stderr_slurp()
49 code := p.code
50 p.close()
51 return os.Result{
52 exit_code: code
53 output: out
54 }
55 }
56 mut collected := ''
57 for p.is_alive() {
58 chunk := p.stderr_read()
59 if chunk.len > 0 {
60 log.write_string(chunk) or {}
61 log.flush()
62 collected += chunk
63 }
64 // drain stdout so the pipe buffer never blocks the child
65 _ := p.stdout_read()
66 time.sleep(100 * time.millisecond)
67 }
68 final := p.stderr_slurp()
69 if final.len > 0 {
70 log.write_string(final) or {}
71 log.flush()
72 collected += final
73 }
74 log.close()
75 p.wait()
76 exit_code := p.code
77 p.close()
78 return os.Result{
79 exit_code: exit_code
80 output: collected
81 }
82}
83
84pub fn Git.show_file_blob(repo_dir string, branch string, file_path string) !string {
85 result := Git.exec_in_dir(repo_dir, ['--no-pager', 'show', '${branch}:${file_path}'])
86 if result.exit_code != 0 {
87 return error(result.output)
88 }
89 return result.output
90}
91
92fn split_command(command string) []string {
93 mut args := []string{}
94 mut current := []u8{}
95 mut quote := u8(0)
96 mut escaped := false
97
98 for ch in command.bytes() {
99 if escaped {
100 current << ch
101 escaped = false
102 continue
103 }
104 if ch == `\\` {
105 escaped = true
106 continue
107 }
108 if quote != 0 {
109 if ch == quote {
110 quote = 0
111 } else {
112 current << ch
113 }
114 continue
115 }
116 if ch == `"` || ch == `'` {
117 quote = ch
118 continue
119 }
120 if ch.is_space() {
121 if current.len > 0 {
122 args << current.bytestr()
123 current.clear()
124 }
125 continue
126 }
127 current << ch
128 }
129 if current.len > 0 {
130 args << current.bytestr()
131 }
132 return args
133}
134