blob: 7b78b21362e619b7bcc8a766ca1d1dbb6bf64a32 [file] [log] [blame]
Haibo Huang24950e72018-06-29 14:53:39 -07001# Copyright (C) 2018 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the 'License');
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an 'AS IS' BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14'''Helper functions to communicate with Git.'''
15
16import datetime
Haibo Huang0d3810f2018-08-31 20:44:25 -070017import re
Haibo Huang24950e72018-06-29 14:53:39 -070018import subprocess
19
20
Haibo Huangc3c0cd42019-01-29 15:24:45 -080021def _run(cmd, cwd, redirect=True):
Haibo Huang24950e72018-06-29 14:53:39 -070022 """Runs a command with stdout and stderr redirected."""
Haibo Huangc3c0cd42019-01-29 15:24:45 -080023 out = subprocess.PIPE if redirect else None
24 return subprocess.run(cmd, stdout=out, stderr=out,
Haibo Huang24950e72018-06-29 14:53:39 -070025 check=True, cwd=cwd)
26
Haibo Huang9dcade42018-08-03 11:52:25 -070027
Haibo Huang24950e72018-06-29 14:53:39 -070028def fetch(proj_path, remote_names):
29 """Runs git fetch.
30
31 Args:
32 proj_path: Path to Git repository.
33 remote_names: Array of string to specify remote names.
34 """
35 _run(['git', 'fetch', '--multiple'] + remote_names, cwd=proj_path)
36
Haibo Huang9dcade42018-08-03 11:52:25 -070037
Haibo Huang24950e72018-06-29 14:53:39 -070038def add_remote(proj_path, name, url):
39 """Adds a git remote.
40
41 Args:
42 proj_path: Path to Git repository.
43 name: Name of the new remote.
44 url: Url of the new remote.
45 """
46 _run(['git', 'remote', 'add', name, url], cwd=proj_path)
47
Haibo Huang9dcade42018-08-03 11:52:25 -070048
Haibo Huang24950e72018-06-29 14:53:39 -070049def list_remotes(proj_path):
50 """Lists all Git remotes.
51
52 Args:
53 proj_path: Path to Git repository.
54
55 Returns:
56 A dict from remote name to remote url.
57 """
58 out = _run(['git', 'remote', '-v'], proj_path)
59 lines = out.stdout.decode('utf-8').splitlines()
60 return dict([line.split()[0:2] for line in lines])
61
Haibo Huang9dcade42018-08-03 11:52:25 -070062
Haibo Huang24950e72018-06-29 14:53:39 -070063def get_commits_ahead(proj_path, branch, base_branch):
64 """Lists commits in `branch` but not `base_branch`."""
Haibo Huang80a05422018-08-27 13:49:42 -070065 out = _run(['git', 'rev-list', '--left-only', '--ancestry-path',
Haibo Huang24950e72018-06-29 14:53:39 -070066 '{}...{}'.format(branch, base_branch)],
67 proj_path)
68 return out.stdout.decode('utf-8').splitlines()
69
Haibo Huang9dcade42018-08-03 11:52:25 -070070
Haibo Huang24950e72018-06-29 14:53:39 -070071def get_commit_time(proj_path, commit):
72 """Gets commit time of one commit."""
73 out = _run(['git', 'show', '-s', '--format=%ct', commit], cwd=proj_path)
74 return datetime.datetime.fromtimestamp(int(out.stdout))
75
Haibo Huang9dcade42018-08-03 11:52:25 -070076
Haibo Huang24950e72018-06-29 14:53:39 -070077def list_remote_branches(proj_path, remote_name):
78 """Lists all branches for a remote."""
79 out = _run(['git', 'branch', '-r'], cwd=proj_path)
80 lines = out.stdout.decode('utf-8').splitlines()
81 stripped = [line.strip() for line in lines]
82 remote_path = remote_name + '/'
83 remote_path_len = len(remote_path)
84 return [line[remote_path_len:] for line in stripped
85 if line.startswith(remote_path)]
Haibo Huang0d3810f2018-08-31 20:44:25 -070086
87
88def _parse_remote_tag(line):
89 tag_prefix = 'refs/tags/'
90 tag_suffix = '^{}'
91 try:
92 line = line[line.index(tag_prefix):]
93 except ValueError:
94 return None
95 line = line[len(tag_prefix):]
96 if line.endswith(tag_suffix):
97 line = line[:-len(tag_suffix)]
98 return line
99
100
101def list_remote_tags(proj_path, remote_name):
102 """Lists all tags for a remote."""
103 out = _run(['git', "ls-remote", "--tags", remote_name],
104 cwd=proj_path)
105 lines = out.stdout.decode('utf-8').splitlines()
106 tags = [_parse_remote_tag(line) for line in lines]
107 return list(set(tags))
108
109
110COMMIT_PATTERN = r'^[a-f0-9]{40}$'
111COMMIT_RE = re.compile(COMMIT_PATTERN)
112
113
114def is_commit(commit):
Haibo Huangcd2c6122018-09-04 14:24:20 -0700115 """Whether a string looks like a SHA1 hash."""
Haibo Huang0d3810f2018-08-31 20:44:25 -0700116 return bool(COMMIT_RE.match(commit))
Haibo Huang0cabc2e2019-01-18 13:29:48 -0800117
118
119def merge(proj_path, branch):
120 """Merges a branch."""
Haibo Huangc3c0cd42019-01-29 15:24:45 -0800121 try:
122 out = _run(['git', 'merge', branch, '--no-commit'],
123 cwd=proj_path)
124 except subprocess.CalledProcessError:
125 # Merge failed. Error is already written to console.
126 subprocess.run(['git', 'merge', '--abort'], cwd=proj_path)
127 raise
128
129
130def add_file(proj_path, file_name):
131 """Stages a file."""
132 _run(['git', 'add', file_name], cwd=proj_path)
133
134
135def delete_branch(proj_path, branch_name):
136 """Force delete a branch."""
137 _run(['git', 'branch', '-D', branch_name], cwd=proj_path)
138
139
140def start_branch(proj_path, branch_name):
141 """Starts a new repo branch."""
142 _run(['repo', 'start', branch_name], cwd=proj_path)
143
144
145def commit(proj_path, message):
146 """Commits changes."""
147 _run(['git', 'commit', '-m', message], cwd=proj_path)
148
149
150def checkout(proj_path, branch_name):
151 """Checkouts a branch."""
152 _run(['git', 'checkout', branch_name], cwd=proj_path)
153
154
155def push(proj_path, remote_name):
156 """Pushes change to remote."""
157 return _run(['git', 'push', remote_name, 'HEAD:refs/for/master'],
158 cwd=proj_path, redirect=False)