blob: 228b3710088374d91a8971d93730f011d5697d72 [file] [log] [blame]
ichern9c913b92019-06-19 10:17:25 -07001# Copyright 2019 The Bazel Authors. All rights reserved.
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"""Code for interacting with git binary to get the file tree checked out at the specified revision.
15"""
16
17_GitRepoInfo = provider(
18 doc = "Provider to organize precomputed arguments for calling git.",
19 fields = {
20 "directory": "Working directory path",
21 "shallow": "Defines the depth of a fetch. Either empty, --depth=1, or --shallow-since=<>",
22 "reset_ref": """Reference to use for resetting the git repository.
23Either commit hash, tag or branch.""",
24 "fetch_ref": """Reference for fetching. Can be empty (HEAD), tag or branch.
25Can not be a commit hash, since typically it is forbidden by git servers.""",
26 "remote": "URL of the git repository to fetch from.",
27 "init_submodules": """If True, submodules update command will be called after fetching
28and resetting to the specified reference.""",
29 },
30)
31
32def git_repo(ctx, directory):
33 """ Fetches data from git repository and checks out file tree.
34
35 Called by git_repository or new_git_repository rules.
36
37 Args:
38 ctx: Context of the calling rules, for reading the attributes.
39 Please refer to the git_repository and new_git_repository rules for the description.
40 directory: Directory where to check out the file tree.
41 Returns:
42 The struct with the following fields:
43 commit: Actual HEAD commit of the checked out data.
44 shallow_since: Actual date and time of the HEAD commit of the checked out data.
45 """
46 if ctx.attr.shallow_since:
47 if ctx.attr.tag:
48 fail("shallow_since not allowed if a tag is specified; --depth=1 will be used for tags")
49 if ctx.attr.branch:
50 fail("shallow_since not allowed if a branch is specified; --depth=1 will be used for branches")
51
52 shallow = "--depth=1"
53 if ctx.attr.commit:
54 # We can not use the commit value in --shallow-since;
55 # And since we are fetching HEAD in this case, we can not use --depth=1
56 shallow = ""
57
58 # Use shallow-since if given
59 if ctx.attr.shallow_since:
60 shallow = "--shallow-since=%s" % ctx.attr.shallow_since
61
62 reset_ref = ""
63 fetch_ref = ""
64 if ctx.attr.commit:
65 reset_ref = ctx.attr.commit
66 elif ctx.attr.tag:
67 reset_ref = "tags/" + ctx.attr.tag
68 fetch_ref = "tags/" + ctx.attr.tag + ":tags/" + ctx.attr.tag
69 elif ctx.attr.branch:
70 reset_ref = "origin/" + ctx.attr.branch
philwo1bcd38a2019-06-24 06:31:31 -070071 fetch_ref = ctx.attr.branch + ":origin/" + ctx.attr.branch
ichern9c913b92019-06-19 10:17:25 -070072
73 git_repo = _GitRepoInfo(
74 directory = ctx.path(directory),
75 shallow = shallow,
76 reset_ref = reset_ref,
77 fetch_ref = fetch_ref,
ichern8c937f32020-07-20 08:15:49 -070078 remote = str(ctx.attr.remote),
ichern9c913b92019-06-19 10:17:25 -070079 init_submodules = ctx.attr.init_submodules,
80 )
81
82 ctx.report_progress("Cloning %s of %s" % (reset_ref, ctx.attr.remote))
83 if (ctx.attr.verbose):
84 print("git.bzl: Cloning or updating %s repository %s using strip_prefix of [%s]" %
85 (
86 " (%s)" % shallow if shallow else "",
87 ctx.name,
88 ctx.attr.strip_prefix if ctx.attr.strip_prefix else "None",
89 ))
90
91 _update(ctx, git_repo)
92 ctx.report_progress("Recording actual commit")
93 actual_commit = _get_head_commit(ctx, git_repo)
94 shallow_date = _get_head_date(ctx, git_repo)
95
96 return struct(commit = actual_commit, shallow_since = shallow_date)
97
98def _update(ctx, git_repo):
99 ctx.delete(git_repo.directory)
100
101 init(ctx, git_repo)
102 add_origin(ctx, git_repo, ctx.attr.remote)
103 fetch(ctx, git_repo)
104 reset(ctx, git_repo)
105 clean(ctx, git_repo)
106
107 if git_repo.init_submodules:
108 ctx.report_progress("Updating submodules")
109 update_submodules(ctx, git_repo)
110
111def init(ctx, git_repo):
brandjon9cf8ebd2019-07-25 08:34:29 -0700112 cl = ["git", "init", str(git_repo.directory)]
ichern9c913b92019-06-19 10:17:25 -0700113 st = ctx.execute(cl, environment = ctx.os.environ)
114 if st.return_code != 0:
115 _error(ctx.name, cl, st.stderr)
116
117def add_origin(ctx, git_repo, remote):
118 _git(ctx, git_repo, "remote", "add", "origin", remote)
119
120def fetch(ctx, git_repo):
121 if not git_repo.fetch_ref:
ichern2c046482019-08-02 07:15:23 -0700122 # We need to explicitly specify to fetch all branches and tags, otherwise only
123 # HEAD-reachable is fetched.
124 # The semantics of --tags flag of git-fetch have changed in Git 1.9, from 1.9 it means
125 # "everything that is already specified and all tags"; before 1.9, it used to mean
126 # "ignore what is specified and fetch all tags".
127 # The arguments below work correctly for both before 1.9 and after 1.9,
128 # as we directly specify the list of references to fetch.
129 _git_maybe_shallow(
130 ctx,
131 git_repo,
132 "fetch",
133 "origin",
134 "refs/heads/*:refs/remotes/origin/*",
135 "refs/tags/*:refs/tags/*",
136 )
ichern9c913b92019-06-19 10:17:25 -0700137 else:
138 _git_maybe_shallow(ctx, git_repo, "fetch", "origin", git_repo.fetch_ref)
139
140def reset(ctx, git_repo):
141 _git(ctx, git_repo, "reset", "--hard", git_repo.reset_ref)
142
143def clean(ctx, git_repo):
144 _git(ctx, git_repo, "clean", "-xdf")
145
146def update_submodules(ctx, git_repo):
147 _git(ctx, git_repo, "submodule", "update", "--init", "--checkout", "--force")
148
149def _get_head_commit(ctx, git_repo):
150 return _git(ctx, git_repo, "log", "-n", "1", "--pretty=format:%H")
151
152def _get_head_date(ctx, git_repo):
153 return _git(ctx, git_repo, "log", "-n", "1", "--pretty=format:%cd", "--date=raw")
154
155def _git(ctx, git_repo, command, *args):
156 start = ["git", command]
157 st = _execute(ctx, git_repo, start + list(args))
158 if st.return_code != 0:
159 _error(ctx.name, start + list(args), st.stderr)
160 return st.stdout
161
162def _git_maybe_shallow(ctx, git_repo, command, *args):
163 start = ["git", command]
164 args_list = list(args)
165 if git_repo.shallow:
166 st = _execute(ctx, git_repo, start + [git_repo.shallow] + args_list)
167 if st.return_code == 0:
168 return
169 st = _execute(ctx, git_repo, start + args_list)
170 if st.return_code != 0:
171 _error(ctx.name, start + args_list, st.stderr)
172
173def _execute(ctx, git_repo, args):
174 return ctx.execute(
175 args,
176 environment = ctx.os.environ,
177 working_directory = str(git_repo.directory),
178 )
179
180def _error(name, command, stderr):
ichern8c937f32020-07-20 08:15:49 -0700181 command_text = " ".join([str(item).strip() for item in command])
182 fail("error running '%s' while working with @%s:\n%s" % (command_text, name, stderr))