##// END OF EJS Templates
git: bumped to latest git version....
super-admin -
r1028:7a402005 default
parent child Browse files
Show More
@@ -1,83 +1,83 b''
1 1 self: super: {
2 2
3 3 # bump GIT version
4 4 git =
5 5 let
6 6 gitWithoutPerl = super.git.override {
7 7 #perlSupport = false;
8 8 };
9 9 in
10 10 super.lib.overrideDerivation gitWithoutPerl (oldAttrs: {
11 11
12 name = "git-2.30.0";
12 name = "git-2.39.1";
13 13 src = self.fetchurl {
14 url = "https://www.kernel.org/pub/software/scm/git/git-2.30.0.tar.xz";
15 sha256 = "06ad6dylgla34k9am7d5z8y3rryc8ln3ibq5z0d74rcm20hm0wsm";
14 url = "https://www.kernel.org/pub/software/scm/git/git-2.39.1.tar.xz";
15 sha256 = "0qf1wly7zagg23svpv533va5v213y7y3lfw76ldkf35k8w48m8s0";
16 16 };
17 17
18 # patches come from: https://github.com/NixOS/nixpkgs/tree/master/pkgs/applications/version-management/git-and-tools/git
18 # patches come from: https://github.com/NixOS/nixpkgs/tree/master/pkgs/applications/version-management/git
19 19 patches = [
20 20 ./patches/git/docbook2texi.patch
21 ./patches/git/git-send-email-honor-PATH.patch
21 22 ./patches/git/git-sh-i18n.patch
23 ./patches/git/installCheck-path.patch
22 24 ./patches/git/ssh-path.patch
23 ./patches/git/git-send-email-honor-PATH.patch
24 ./patches/git/installCheck-path.patch
25 25 ];
26 26
27 27 #preInstallCheck = oldAttrs.preInstallCheck + ''
28 28 # disable_test t4129-apply-samemode
29 29 # disable_test t5324-split-commit-graph
30 30 #'';
31 31
32 32 });
33 33
34 34 libgit2rc = super.lib.overrideDerivation super.libgit2 (oldAttrs: {
35 35 name = "libgit2-0.28.2";
36 36 version = "0.28.2";
37 37
38 38 src = self.fetchFromGitHub {
39 39 owner = "libgit2";
40 40 repo = "libgit2";
41 41 rev = "v0.28.2";
42 42 sha256 = "0cm8fvs05rj0baigs2133q5a0sm3pa234y8h6hmwhl2bz9xq3k4b";
43 43 };
44 44
45 45 cmakeFlags = [ "-DTHREADSAFE=ON" "-DUSE_HTTPS=no"];
46 46
47 47 buildInputs = [
48 48 super.zlib
49 49 super.libssh2
50 50 super.openssl
51 51 super.curl
52 52 ];
53 53
54 54
55 55 });
56 56
57 57 # Override subversion derivation to
58 58 # - activate python bindings
59 59 subversion =
60 60 let
61 61 subversionWithPython = super.subversion.override {
62 62 httpSupport = true;
63 63 pythonBindings = true;
64 64 python = self.python27Packages.python;
65 65 };
66 66 in
67 67 super.lib.overrideDerivation subversionWithPython (oldAttrs: {
68 68 name = "subversion-1.13.0";
69 69 src = self.fetchurl {
70 70 url = "https://archive.apache.org/dist/subversion/subversion-1.13.0.tar.gz";
71 71 sha256 = "0cb9p7f5hg0l4k32hz8vmvy2r45igchq5sh4m366za5q0c649bfs";
72 72 };
73 73
74 74 ## use internal lz4/utf8proc because it is stable and shipped with SVN
75 75 configureFlags = oldAttrs.configureFlags ++ [
76 76 " --with-lz4=internal"
77 77 " --with-utf8proc=internal"
78 78 ];
79 79
80 80 });
81 81
82 82
83 83 }
@@ -1,38 +1,38 b''
1 1 This patch does two things: (1) use the right name for `docbook2texi',
2 2 and (2) make sure `gitman.info' isn't produced since it's broken (duplicate
3 3 node names).
4 4
5 5 diff --git a/Documentation/Makefile b/Documentation/Makefile
6 6 index 26a2342bea..ceccd67ebb 100644
7 7 --- a/Documentation/Makefile
8 8 +++ b/Documentation/Makefile
9 9 @@ -132,7 +132,7 @@ HTML_REPO = ../../git-htmldocs
10 10
11 11 MAKEINFO = makeinfo
12 12 INSTALL_INFO = install-info
13 13 -DOCBOOK2X_TEXI = docbook2x-texi
14 14 +DOCBOOK2X_TEXI = docbook2texi
15 15 DBLATEX = dblatex
16 16 ASCIIDOC_DBLATEX_DIR = /etc/asciidoc/dblatex
17 17 DBLATEX_COMMON = -p $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.xsl -s $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.sty
18 18 @@ -250,7 +250,7 @@ man1: $(DOC_MAN1)
19 19 man5: $(DOC_MAN5)
20 20 man7: $(DOC_MAN7)
21 21
22 22 -info: git.info gitman.info
23 23 +info: git.info
24 24
25 25 pdf: user-manual.pdf
26 26
27 27 @@ -266,10 +266,9 @@ install-man: man
28 28
29 29 install-info: info
30 30 $(INSTALL) -d -m 755 $(DESTDIR)$(infodir)
31 31 - $(INSTALL) -m 644 git.info gitman.info $(DESTDIR)$(infodir)
32 32 + $(INSTALL) -m 644 git.info $(DESTDIR)$(infodir)
33 33 if test -r $(DESTDIR)$(infodir)/dir; then \
34 34 $(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) git.info ;\
35 35 - $(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) gitman.info ;\
36 36 else \
37 37 echo "No directory found in $(DESTDIR)$(infodir)" >&2 ; \
38 fi
38 fi No newline at end of file
@@ -1,28 +1,31 b''
1 1 diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
2 index 1afe9fc858..05dd7c3a90 100644
2 index 3db4eab4ba..39bc0e77c9 100644
3 3 --- a/Documentation/git-send-email.txt
4 4 +++ b/Documentation/git-send-email.txt
5 @@ -215,8 +215,7 @@ a password is obtained using 'git-credential'.
6 specify a full pathname of a sendmail-like program instead;
7 the program must support the `-i` option. Default value can
8 be specified by the `sendemail.smtpServer` configuration
9 - option; the built-in default is to search for `sendmail` in
10 - `/usr/sbin`, `/usr/lib` and $PATH if such program is
11 + option; the built-in default is to search in $PATH if such program is
12 available, falling back to `localhost` otherwise.
13
14 --smtp-server-port=<port>::
5 @@ -220,9 +220,9 @@ a password is obtained using 'git-credential'.
6 --smtp-server=<host>::
7 If set, specifies the outgoing SMTP server to use (e.g.
8 `smtp.example.com` or a raw IP address). If unspecified, and if
9 - `--sendmail-cmd` is also unspecified, the default is to search
10 - for `sendmail` in `/usr/sbin`, `/usr/lib` and $PATH if such a
11 - program is available, falling back to `localhost` otherwise.
12 + `--sendmail-cmd` is also unspecified, the default is to search for
13 + `sendmail` in $PATH if such a program is available, falling back to
14 + `localhost` otherwise.
15 +
16 For backward compatibility, this option can also specify a full pathname
17 of a sendmail-like program instead; the program must support the `-i`
15 18 diff --git a/git-send-email.perl b/git-send-email.perl
16 index 8eb63b5a2f..74a61d8213 100755
19 index e65d969d0b..508d49483d 100755
17 20 --- a/git-send-email.perl
18 21 +++ b/git-send-email.perl
19 @@ -956,8 +956,7 @@ sub expand_one_alias {
22 @@ -1066,8 +1066,7 @@ sub expand_one_alias {
20 23 }
21 24
22 if (!defined $smtp_server) {
25 if (!defined $sendmail_cmd && !defined $smtp_server) {
23 26 - my @sendmail_paths = qw( /usr/sbin/sendmail /usr/lib/sendmail );
24 27 - push @sendmail_paths, map {"$_/sendmail"} split /:/, $ENV{PATH};
25 28 + my @sendmail_paths = map {"$_/sendmail"} split /:/, $ENV{PATH};
26 29 foreach (@sendmail_paths) {
27 30 if (-x $_) {
28 $smtp_server = $_;
31 $sendmail_cmd = $_; No newline at end of file
@@ -1,23 +1,23 b''
1 1 diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh
2 2 index e1d917fd27..e90f8e1414 100644
3 3 --- a/git-sh-i18n.sh
4 4 +++ b/git-sh-i18n.sh
5 5 @@ -26,7 +26,7 @@ then
6 6 elif test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
7 7 then
8 8 : no probing necessary
9 9 -elif type gettext.sh >/dev/null 2>&1
10 10 +elif type @gettext@/bin/gettext.sh >/dev/null 2>&1
11 11 then
12 12 # GNU libintl's gettext.sh
13 13 GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu
14 14 @@ -43,7 +43,8 @@ export GIT_INTERNAL_GETTEXT_SH_SCHEME
15 15 case "$GIT_INTERNAL_GETTEXT_SH_SCHEME" in
16 16 gnu)
17 17 # Use libintl's gettext.sh, or fall back to English if we can't.
18 18 - . gettext.sh
19 19 + . @gettext@/bin/gettext.sh
20 20 + export PATH=@gettext@/bin:$PATH
21 21 ;;
22 22 gettext_without_eval_gettext)
23 # Solaris has a gettext(1) but no eval_gettext(1)
23 # Solaris has a gettext(1) but no eval_gettext(1) No newline at end of file
@@ -1,13 +1,13 b''
1 1 diff --git a/t/test-lib.sh b/t/test-lib.sh
2 2 index 8665b0a9b6..8bb892b1af 100644
3 3 --- a/t/test-lib.sh
4 4 +++ b/t/test-lib.sh
5 5 @@ -1227,7 +1227,7 @@ elif test -n "$GIT_TEST_INSTALLED"
6 6 then
7 7 GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) ||
8 8 error "Cannot run git from $GIT_TEST_INSTALLED."
9 9 - PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR/t/helper:$PATH
10 10 + PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR/t/helper:$GIT_BUILD_DIR:$PATH
11 11 GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
12 12 else # normal case, use ../bin-wrappers only unless $with_dashes:
13 if test -n "$no_bin_wrappers"
13 if test -n "$no_bin_wrappers" No newline at end of file
@@ -1,26 +1,26 b''
1 1 diff --git a/connect.c b/connect.c
2 2 index 4813f005ab..b3f12f3268 100644
3 3 --- a/connect.c
4 4 +++ b/connect.c
5 5 @@ -1183,7 +1183,7 @@ static void fill_ssh_args(struct child_process *conn, const char *ssh_host,
6 6
7 7 ssh = getenv("GIT_SSH");
8 8 if (!ssh)
9 9 - ssh = "ssh";
10 10 + ssh = "@ssh@";
11 11 variant = determine_ssh_variant(ssh, 0);
12 12 }
13 13
14 14 diff --git a/git-gui/lib/remote_add.tcl b/git-gui/lib/remote_add.tcl
15 15 index 480a6b30d0..7817204241 100644
16 16 --- a/git-gui/lib/remote_add.tcl
17 17 +++ b/git-gui/lib/remote_add.tcl
18 18 @@ -139,7 +139,7 @@ method _add {} {
19 19 # Parse the location
20 20 if { [regexp {(?:git\+)?ssh://([^/]+)(/.+)} $location xx host path]
21 21 || [regexp {([^:][^:]+):(.+)} $location xx host path]} {
22 22 - set ssh ssh
23 23 + set ssh @ssh@
24 24 if {[info exists env(GIT_SSH)]} {
25 25 set ssh $env(GIT_SSH)
26 }
26 } No newline at end of file
@@ -1,1281 +1,1281 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2020 RhodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import collections
19 19 import logging
20 20 import os
21 21 import posixpath as vcspath
22 22 import re
23 23 import stat
24 24 import traceback
25 25 import urllib
26 26 import urllib2
27 27 from functools import wraps
28 28
29 29 import more_itertools
30 30 import pygit2
31 31 from pygit2 import Repository as LibGit2Repo
32 32 from pygit2 import index as LibGit2Index
33 33 from dulwich import index, objects
34 34 from dulwich.client import HttpGitClient, LocalGitClient
35 35 from dulwich.errors import (
36 36 NotGitRepository, ChecksumMismatch, WrongObjectException,
37 37 MissingCommitError, ObjectMissing, HangupException,
38 38 UnexpectedCommandError)
39 39 from dulwich.repo import Repo as DulwichRepo
40 40 from dulwich.server import update_server_info
41 41
42 42 from vcsserver import exceptions, settings, subprocessio
43 43 from vcsserver.utils import safe_str, safe_int, safe_unicode
44 44 from vcsserver.base import RepoFactory, obfuscate_qs, ArchiveNode, archive_repo
45 45 from vcsserver.hgcompat import (
46 46 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
47 47 from vcsserver.git_lfs.lib import LFSOidStore
48 48 from vcsserver.vcs_base import RemoteBase
49 49
50 50 DIR_STAT = stat.S_IFDIR
51 51 FILE_MODE = stat.S_IFMT
52 52 GIT_LINK = objects.S_IFGITLINK
53 53 PEELED_REF_MARKER = '^{}'
54 54
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 def str_to_dulwich(value):
60 60 """
61 61 Dulwich 0.10.1a requires `unicode` objects to be passed in.
62 62 """
63 63 return value.decode(settings.WIRE_ENCODING)
64 64
65 65
66 66 def reraise_safe_exceptions(func):
67 67 """Converts Dulwich exceptions to something neutral."""
68 68
69 69 @wraps(func)
70 70 def wrapper(*args, **kwargs):
71 71 try:
72 72 return func(*args, **kwargs)
73 73 except (ChecksumMismatch, WrongObjectException, MissingCommitError, ObjectMissing,) as e:
74 74 exc = exceptions.LookupException(org_exc=e)
75 75 raise exc(safe_str(e))
76 76 except (HangupException, UnexpectedCommandError) as e:
77 77 exc = exceptions.VcsException(org_exc=e)
78 78 raise exc(safe_str(e))
79 79 except Exception as e:
80 80 # NOTE(marcink): becuase of how dulwich handles some exceptions
81 81 # (KeyError on empty repos), we cannot track this and catch all
82 82 # exceptions, it's an exceptions from other handlers
83 83 #if not hasattr(e, '_vcs_kind'):
84 84 #log.exception("Unhandled exception in git remote call")
85 85 #raise_from_original(exceptions.UnhandledException)
86 86 raise
87 87 return wrapper
88 88
89 89
90 90 class Repo(DulwichRepo):
91 91 """
92 92 A wrapper for dulwich Repo class.
93 93
94 94 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
95 95 "Too many open files" error. We need to close all opened file descriptors
96 96 once the repo object is destroyed.
97 97 """
98 98 def __del__(self):
99 99 if hasattr(self, 'object_store'):
100 100 self.close()
101 101
102 102
103 103 class Repository(LibGit2Repo):
104 104
105 105 def __enter__(self):
106 106 return self
107 107
108 108 def __exit__(self, exc_type, exc_val, exc_tb):
109 109 self.free()
110 110
111 111
112 112 class GitFactory(RepoFactory):
113 113 repo_type = 'git'
114 114
115 115 def _create_repo(self, wire, create, use_libgit2=False):
116 116 if use_libgit2:
117 117 return Repository(wire['path'])
118 118 else:
119 119 repo_path = str_to_dulwich(wire['path'])
120 120 return Repo(repo_path)
121 121
122 122 def repo(self, wire, create=False, use_libgit2=False):
123 123 """
124 124 Get a repository instance for the given path.
125 125 """
126 126 return self._create_repo(wire, create, use_libgit2)
127 127
128 128 def repo_libgit2(self, wire):
129 129 return self.repo(wire, use_libgit2=True)
130 130
131 131
132 132 class GitRemote(RemoteBase):
133 133
134 134 def __init__(self, factory):
135 135 self._factory = factory
136 136 self._bulk_methods = {
137 137 "date": self.date,
138 138 "author": self.author,
139 139 "branch": self.branch,
140 140 "message": self.message,
141 141 "parents": self.parents,
142 142 "_commit": self.revision,
143 143 }
144 144
145 145 def _wire_to_config(self, wire):
146 146 if 'config' in wire:
147 147 return dict([(x[0] + '_' + x[1], x[2]) for x in wire['config']])
148 148 return {}
149 149
150 150 def _remote_conf(self, config):
151 151 params = [
152 152 '-c', 'core.askpass=""',
153 153 ]
154 154 ssl_cert_dir = config.get('vcs_ssl_dir')
155 155 if ssl_cert_dir:
156 156 params.extend(['-c', 'http.sslCAinfo={}'.format(ssl_cert_dir)])
157 157 return params
158 158
159 159 @reraise_safe_exceptions
160 160 def discover_git_version(self):
161 161 stdout, _ = self.run_git_command(
162 162 {}, ['--version'], _bare=True, _safe=True)
163 163 prefix = 'git version'
164 164 if stdout.startswith(prefix):
165 165 stdout = stdout[len(prefix):]
166 166 return stdout.strip()
167 167
168 168 @reraise_safe_exceptions
169 169 def is_empty(self, wire):
170 170 repo_init = self._factory.repo_libgit2(wire)
171 171 with repo_init as repo:
172 172
173 173 try:
174 174 has_head = repo.head.name
175 175 if has_head:
176 176 return False
177 177
178 178 # NOTE(marcink): check again using more expensive method
179 179 return repo.is_empty
180 180 except Exception:
181 181 pass
182 182
183 183 return True
184 184
185 185 @reraise_safe_exceptions
186 186 def assert_correct_path(self, wire):
187 187 cache_on, context_uid, repo_id = self._cache_on(wire)
188 188 region = self._region(wire)
189 189 @region.conditional_cache_on_arguments(condition=cache_on)
190 190 def _assert_correct_path(_context_uid, _repo_id):
191 191 try:
192 192 repo_init = self._factory.repo_libgit2(wire)
193 193 with repo_init as repo:
194 194 pass
195 195 except pygit2.GitError:
196 196 path = wire.get('path')
197 197 tb = traceback.format_exc()
198 198 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
199 199 return False
200 200
201 201 return True
202 202 return _assert_correct_path(context_uid, repo_id)
203 203
204 204 @reraise_safe_exceptions
205 205 def bare(self, wire):
206 206 repo_init = self._factory.repo_libgit2(wire)
207 207 with repo_init as repo:
208 208 return repo.is_bare
209 209
210 210 @reraise_safe_exceptions
211 211 def blob_as_pretty_string(self, wire, sha):
212 212 repo_init = self._factory.repo_libgit2(wire)
213 213 with repo_init as repo:
214 214 blob_obj = repo[sha]
215 215 blob = blob_obj.data
216 216 return blob
217 217
218 218 @reraise_safe_exceptions
219 219 def blob_raw_length(self, wire, sha):
220 220 cache_on, context_uid, repo_id = self._cache_on(wire)
221 221 region = self._region(wire)
222 222 @region.conditional_cache_on_arguments(condition=cache_on)
223 223 def _blob_raw_length(_repo_id, _sha):
224 224
225 225 repo_init = self._factory.repo_libgit2(wire)
226 226 with repo_init as repo:
227 227 blob = repo[sha]
228 228 return blob.size
229 229
230 230 return _blob_raw_length(repo_id, sha)
231 231
232 232 def _parse_lfs_pointer(self, raw_content):
233 233
234 234 spec_string = 'version https://git-lfs.github.com/spec'
235 235 if raw_content and raw_content.startswith(spec_string):
236 236 pattern = re.compile(r"""
237 237 (?:\n)?
238 238 ^version[ ]https://git-lfs\.github\.com/spec/(?P<spec_ver>v\d+)\n
239 239 ^oid[ ] sha256:(?P<oid_hash>[0-9a-f]{64})\n
240 240 ^size[ ](?P<oid_size>[0-9]+)\n
241 241 (?:\n)?
242 242 """, re.VERBOSE | re.MULTILINE)
243 243 match = pattern.match(raw_content)
244 244 if match:
245 245 return match.groupdict()
246 246
247 247 return {}
248 248
249 249 @reraise_safe_exceptions
250 250 def is_large_file(self, wire, commit_id):
251 251 cache_on, context_uid, repo_id = self._cache_on(wire)
252 252
253 253 region = self._region(wire)
254 254 @region.conditional_cache_on_arguments(condition=cache_on)
255 255 def _is_large_file(_repo_id, _sha):
256 256 repo_init = self._factory.repo_libgit2(wire)
257 257 with repo_init as repo:
258 258 blob = repo[commit_id]
259 259 if blob.is_binary:
260 260 return {}
261 261
262 262 return self._parse_lfs_pointer(blob.data)
263 263
264 264 return _is_large_file(repo_id, commit_id)
265 265
266 266 @reraise_safe_exceptions
267 267 def is_binary(self, wire, tree_id):
268 268 cache_on, context_uid, repo_id = self._cache_on(wire)
269 269
270 270 region = self._region(wire)
271 271 @region.conditional_cache_on_arguments(condition=cache_on)
272 272 def _is_binary(_repo_id, _tree_id):
273 273 repo_init = self._factory.repo_libgit2(wire)
274 274 with repo_init as repo:
275 275 blob_obj = repo[tree_id]
276 276 return blob_obj.is_binary
277 277
278 278 return _is_binary(repo_id, tree_id)
279 279
280 280 @reraise_safe_exceptions
281 281 def in_largefiles_store(self, wire, oid):
282 282 conf = self._wire_to_config(wire)
283 283 repo_init = self._factory.repo_libgit2(wire)
284 284 with repo_init as repo:
285 285 repo_name = repo.path
286 286
287 287 store_location = conf.get('vcs_git_lfs_store_location')
288 288 if store_location:
289 289
290 290 store = LFSOidStore(
291 291 oid=oid, repo=repo_name, store_location=store_location)
292 292 return store.has_oid()
293 293
294 294 return False
295 295
296 296 @reraise_safe_exceptions
297 297 def store_path(self, wire, oid):
298 298 conf = self._wire_to_config(wire)
299 299 repo_init = self._factory.repo_libgit2(wire)
300 300 with repo_init as repo:
301 301 repo_name = repo.path
302 302
303 303 store_location = conf.get('vcs_git_lfs_store_location')
304 304 if store_location:
305 305 store = LFSOidStore(
306 306 oid=oid, repo=repo_name, store_location=store_location)
307 307 return store.oid_path
308 308 raise ValueError('Unable to fetch oid with path {}'.format(oid))
309 309
310 310 @reraise_safe_exceptions
311 311 def bulk_request(self, wire, rev, pre_load):
312 312 cache_on, context_uid, repo_id = self._cache_on(wire)
313 313 region = self._region(wire)
314 314 @region.conditional_cache_on_arguments(condition=cache_on)
315 315 def _bulk_request(_repo_id, _rev, _pre_load):
316 316 result = {}
317 317 for attr in pre_load:
318 318 try:
319 319 method = self._bulk_methods[attr]
320 320 args = [wire, rev]
321 321 result[attr] = method(*args)
322 322 except KeyError as e:
323 323 raise exceptions.VcsException(e)(
324 324 "Unknown bulk attribute: %s" % attr)
325 325 return result
326 326
327 327 return _bulk_request(repo_id, rev, sorted(pre_load))
328 328
329 329 def _build_opener(self, url):
330 330 handlers = []
331 331 url_obj = url_parser(url)
332 332 _, authinfo = url_obj.authinfo()
333 333
334 334 if authinfo:
335 335 # create a password manager
336 336 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
337 337 passmgr.add_password(*authinfo)
338 338
339 339 handlers.extend((httpbasicauthhandler(passmgr),
340 340 httpdigestauthhandler(passmgr)))
341 341
342 342 return urllib2.build_opener(*handlers)
343 343
344 344 def _type_id_to_name(self, type_id):
345 345 return {
346 346 1: b'commit',
347 347 2: b'tree',
348 348 3: b'blob',
349 349 4: b'tag'
350 350 }[type_id]
351 351
352 352 @reraise_safe_exceptions
353 353 def check_url(self, url, config):
354 354 url_obj = url_parser(url)
355 355 test_uri, _ = url_obj.authinfo()
356 356 url_obj.passwd = '*****' if url_obj.passwd else url_obj.passwd
357 357 url_obj.query = obfuscate_qs(url_obj.query)
358 358 cleaned_uri = str(url_obj)
359 359 log.info("Checking URL for remote cloning/import: %s", cleaned_uri)
360 360
361 361 if not test_uri.endswith('info/refs'):
362 362 test_uri = test_uri.rstrip('/') + '/info/refs'
363 363
364 364 o = self._build_opener(url)
365 365 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
366 366
367 367 q = {"service": 'git-upload-pack'}
368 368 qs = '?%s' % urllib.urlencode(q)
369 369 cu = "%s%s" % (test_uri, qs)
370 370 req = urllib2.Request(cu, None, {})
371 371
372 372 try:
373 373 log.debug("Trying to open URL %s", cleaned_uri)
374 374 resp = o.open(req)
375 375 if resp.code != 200:
376 376 raise exceptions.URLError()('Return Code is not 200')
377 377 except Exception as e:
378 378 log.warning("URL cannot be opened: %s", cleaned_uri, exc_info=True)
379 379 # means it cannot be cloned
380 380 raise exceptions.URLError(e)("[%s] org_exc: %s" % (cleaned_uri, e))
381 381
382 382 # now detect if it's proper git repo
383 383 gitdata = resp.read()
384 384 if 'service=git-upload-pack' in gitdata:
385 385 pass
386 386 elif re.findall(r'[0-9a-fA-F]{40}\s+refs', gitdata):
387 387 # old style git can return some other format !
388 388 pass
389 389 else:
390 390 raise exceptions.URLError()(
391 391 "url [%s] does not look like an git" % (cleaned_uri,))
392 392
393 393 return True
394 394
395 395 @reraise_safe_exceptions
396 396 def clone(self, wire, url, deferred, valid_refs, update_after_clone):
397 397 # TODO(marcink): deprecate this method. Last i checked we don't use it anymore
398 398 remote_refs = self.pull(wire, url, apply_refs=False)
399 399 repo = self._factory.repo(wire)
400 400 if isinstance(valid_refs, list):
401 401 valid_refs = tuple(valid_refs)
402 402
403 403 for k in remote_refs:
404 404 # only parse heads/tags and skip so called deferred tags
405 405 if k.startswith(valid_refs) and not k.endswith(deferred):
406 406 repo[k] = remote_refs[k]
407 407
408 408 if update_after_clone:
409 409 # we want to checkout HEAD
410 410 repo["HEAD"] = remote_refs["HEAD"]
411 411 index.build_index_from_tree(repo.path, repo.index_path(),
412 412 repo.object_store, repo["HEAD"].tree)
413 413
414 414 @reraise_safe_exceptions
415 415 def branch(self, wire, commit_id):
416 416 cache_on, context_uid, repo_id = self._cache_on(wire)
417 417 region = self._region(wire)
418 418 @region.conditional_cache_on_arguments(condition=cache_on)
419 419 def _branch(_context_uid, _repo_id, _commit_id):
420 420 regex = re.compile('^refs/heads')
421 421
422 422 def filter_with(ref):
423 423 return regex.match(ref[0]) and ref[1] == _commit_id
424 424
425 425 branches = filter(filter_with, self.get_refs(wire).items())
426 426 return [x[0].split('refs/heads/')[-1] for x in branches]
427 427
428 428 return _branch(context_uid, repo_id, commit_id)
429 429
430 430 @reraise_safe_exceptions
431 431 def commit_branches(self, wire, commit_id):
432 432 cache_on, context_uid, repo_id = self._cache_on(wire)
433 433 region = self._region(wire)
434 434 @region.conditional_cache_on_arguments(condition=cache_on)
435 435 def _commit_branches(_context_uid, _repo_id, _commit_id):
436 436 repo_init = self._factory.repo_libgit2(wire)
437 437 with repo_init as repo:
438 438 branches = [x for x in repo.branches.with_commit(_commit_id)]
439 439 return branches
440 440
441 441 return _commit_branches(context_uid, repo_id, commit_id)
442 442
443 443 @reraise_safe_exceptions
444 444 def add_object(self, wire, content):
445 445 repo_init = self._factory.repo_libgit2(wire)
446 446 with repo_init as repo:
447 447 blob = objects.Blob()
448 448 blob.set_raw_string(content)
449 449 repo.object_store.add_object(blob)
450 450 return blob.id
451 451
452 452 # TODO: this is quite complex, check if that can be simplified
453 453 @reraise_safe_exceptions
454 454 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
455 455 # Defines the root tree
456 456 class _Root(object):
457 457 def __repr__(self):
458 458 return 'ROOT TREE'
459 459 ROOT = _Root()
460 460
461 461 repo = self._factory.repo(wire)
462 462 object_store = repo.object_store
463 463
464 464 # Create tree and populates it with blobs
465 465
466 466 if commit_tree and repo[commit_tree]:
467 467 git_commit = repo[commit_data['parents'][0]]
468 468 commit_tree = repo[git_commit.tree] # root tree
469 469 else:
470 470 commit_tree = objects.Tree()
471 471
472 472 for node in updated:
473 473 # Compute subdirs if needed
474 474 dirpath, nodename = vcspath.split(node['path'])
475 475 dirnames = map(safe_str, dirpath and dirpath.split('/') or [])
476 476 parent = commit_tree
477 477 ancestors = [('', parent)]
478 478
479 479 # Tries to dig for the deepest existing tree
480 480 while dirnames:
481 481 curdir = dirnames.pop(0)
482 482 try:
483 483 dir_id = parent[curdir][1]
484 484 except KeyError:
485 485 # put curdir back into dirnames and stops
486 486 dirnames.insert(0, curdir)
487 487 break
488 488 else:
489 489 # If found, updates parent
490 490 parent = repo[dir_id]
491 491 ancestors.append((curdir, parent))
492 492 # Now parent is deepest existing tree and we need to create
493 493 # subtrees for dirnames (in reverse order)
494 494 # [this only applies for nodes from added]
495 495 new_trees = []
496 496
497 497 blob = objects.Blob.from_string(node['content'])
498 498
499 499 if dirnames:
500 500 # If there are trees which should be created we need to build
501 501 # them now (in reverse order)
502 502 reversed_dirnames = list(reversed(dirnames))
503 503 curtree = objects.Tree()
504 504 curtree[node['node_path']] = node['mode'], blob.id
505 505 new_trees.append(curtree)
506 506 for dirname in reversed_dirnames[:-1]:
507 507 newtree = objects.Tree()
508 508 newtree[dirname] = (DIR_STAT, curtree.id)
509 509 new_trees.append(newtree)
510 510 curtree = newtree
511 511 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
512 512 else:
513 513 parent.add(name=node['node_path'], mode=node['mode'], hexsha=blob.id)
514 514
515 515 new_trees.append(parent)
516 516 # Update ancestors
517 517 reversed_ancestors = reversed(
518 518 [(a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])])
519 519 for parent, tree, path in reversed_ancestors:
520 520 parent[path] = (DIR_STAT, tree.id)
521 521 object_store.add_object(tree)
522 522
523 523 object_store.add_object(blob)
524 524 for tree in new_trees:
525 525 object_store.add_object(tree)
526 526
527 527 for node_path in removed:
528 528 paths = node_path.split('/')
529 529 tree = commit_tree # start with top-level
530 530 trees = [{'tree': tree, 'path': ROOT}]
531 531 # Traverse deep into the forest...
532 532 # resolve final tree by iterating the path.
533 533 # e.g a/b/c.txt will get
534 534 # - root as tree then
535 535 # - 'a' as tree,
536 536 # - 'b' as tree,
537 537 # - stop at c as blob.
538 538 for path in paths:
539 539 try:
540 540 obj = repo[tree[path][1]]
541 541 if isinstance(obj, objects.Tree):
542 542 trees.append({'tree': obj, 'path': path})
543 543 tree = obj
544 544 except KeyError:
545 545 break
546 546 #PROBLEM:
547 547 """
548 548 We're not editing same reference tree object
549 549 """
550 550 # Cut down the blob and all rotten trees on the way back...
551 551 for path, tree_data in reversed(zip(paths, trees)):
552 552 tree = tree_data['tree']
553 553 tree.__delitem__(path)
554 554 # This operation edits the tree, we need to mark new commit back
555 555
556 556 if len(tree) > 0:
557 557 # This tree still has elements - don't remove it or any
558 558 # of it's parents
559 559 break
560 560
561 561 object_store.add_object(commit_tree)
562 562
563 563 # Create commit
564 564 commit = objects.Commit()
565 565 commit.tree = commit_tree.id
566 566 for k, v in commit_data.items():
567 567 setattr(commit, k, v)
568 568 object_store.add_object(commit)
569 569
570 570 self.create_branch(wire, branch, commit.id)
571 571
572 572 # dulwich set-ref
573 573 ref = 'refs/heads/%s' % branch
574 574 repo.refs[ref] = commit.id
575 575
576 576 return commit.id
577 577
578 578 @reraise_safe_exceptions
579 579 def pull(self, wire, url, apply_refs=True, refs=None, update_after=False):
580 580 if url != 'default' and '://' not in url:
581 581 client = LocalGitClient(url)
582 582 else:
583 583 url_obj = url_parser(url)
584 584 o = self._build_opener(url)
585 585 url, _ = url_obj.authinfo()
586 586 client = HttpGitClient(base_url=url, opener=o)
587 587 repo = self._factory.repo(wire)
588 588
589 589 determine_wants = repo.object_store.determine_wants_all
590 590 if refs:
591 591 def determine_wants_requested(references):
592 592 return [references[r] for r in references if r in refs]
593 593 determine_wants = determine_wants_requested
594 594
595 595 try:
596 596 remote_refs = client.fetch(
597 597 path=url, target=repo, determine_wants=determine_wants)
598 598 except NotGitRepository as e:
599 599 log.warning(
600 600 'Trying to fetch from "%s" failed, not a Git repository.', url)
601 601 # Exception can contain unicode which we convert
602 602 raise exceptions.AbortException(e)(repr(e))
603 603
604 604 # mikhail: client.fetch() returns all the remote refs, but fetches only
605 605 # refs filtered by `determine_wants` function. We need to filter result
606 606 # as well
607 607 if refs:
608 608 remote_refs = {k: remote_refs[k] for k in remote_refs if k in refs}
609 609
610 610 if apply_refs:
611 611 # TODO: johbo: Needs proper test coverage with a git repository
612 612 # that contains a tag object, so that we would end up with
613 613 # a peeled ref at this point.
614 614 for k in remote_refs:
615 615 if k.endswith(PEELED_REF_MARKER):
616 616 log.debug("Skipping peeled reference %s", k)
617 617 continue
618 618 repo[k] = remote_refs[k]
619 619
620 620 if refs and not update_after:
621 621 # mikhail: explicitly set the head to the last ref.
622 622 repo["HEAD"] = remote_refs[refs[-1]]
623 623
624 624 if update_after:
625 625 # we want to checkout HEAD
626 626 repo["HEAD"] = remote_refs["HEAD"]
627 627 index.build_index_from_tree(repo.path, repo.index_path(),
628 628 repo.object_store, repo["HEAD"].tree)
629 629 return remote_refs
630 630
631 631 @reraise_safe_exceptions
632 632 def sync_fetch(self, wire, url, refs=None, all_refs=False):
633 633 repo = self._factory.repo(wire)
634 634 if refs and not isinstance(refs, (list, tuple)):
635 635 refs = [refs]
636 636
637 637 config = self._wire_to_config(wire)
638 638 # get all remote refs we'll use to fetch later
639 639 cmd = ['ls-remote']
640 640 if not all_refs:
641 641 cmd += ['--heads', '--tags']
642 642 cmd += [url]
643 643 output, __ = self.run_git_command(
644 644 wire, cmd, fail_on_stderr=False,
645 645 _copts=self._remote_conf(config),
646 646 extra_env={'GIT_TERMINAL_PROMPT': '0'})
647 647
648 648 remote_refs = collections.OrderedDict()
649 649 fetch_refs = []
650 650
651 651 for ref_line in output.splitlines():
652 652 sha, ref = ref_line.split('\t')
653 653 sha = sha.strip()
654 654 if ref in remote_refs:
655 655 # duplicate, skip
656 656 continue
657 657 if ref.endswith(PEELED_REF_MARKER):
658 658 log.debug("Skipping peeled reference %s", ref)
659 659 continue
660 660 # don't sync HEAD
661 661 if ref in ['HEAD']:
662 662 continue
663 663
664 664 remote_refs[ref] = sha
665 665
666 666 if refs and sha in refs:
667 667 # we filter fetch using our specified refs
668 668 fetch_refs.append('{}:{}'.format(ref, ref))
669 669 elif not refs:
670 670 fetch_refs.append('{}:{}'.format(ref, ref))
671 671 log.debug('Finished obtaining fetch refs, total: %s', len(fetch_refs))
672 672
673 673 if fetch_refs:
674 674 for chunk in more_itertools.chunked(fetch_refs, 1024 * 4):
675 675 fetch_refs_chunks = list(chunk)
676 676 log.debug('Fetching %s refs from import url', len(fetch_refs_chunks))
677 677 _out, _err = self.run_git_command(
678 678 wire, ['fetch', url, '--force', '--prune', '--'] + fetch_refs_chunks,
679 679 fail_on_stderr=False,
680 680 _copts=self._remote_conf(config),
681 681 extra_env={'GIT_TERMINAL_PROMPT': '0'})
682 682
683 683 return remote_refs
684 684
685 685 @reraise_safe_exceptions
686 686 def sync_push(self, wire, url, refs=None):
687 687 if not self.check_url(url, wire):
688 688 return
689 689 config = self._wire_to_config(wire)
690 690 self._factory.repo(wire)
691 691 self.run_git_command(
692 692 wire, ['push', url, '--mirror'], fail_on_stderr=False,
693 693 _copts=self._remote_conf(config),
694 694 extra_env={'GIT_TERMINAL_PROMPT': '0'})
695 695
696 696 @reraise_safe_exceptions
697 697 def get_remote_refs(self, wire, url):
698 698 repo = Repo(url)
699 699 return repo.get_refs()
700 700
701 701 @reraise_safe_exceptions
702 702 def get_description(self, wire):
703 703 repo = self._factory.repo(wire)
704 704 return repo.get_description()
705 705
706 706 @reraise_safe_exceptions
707 707 def get_missing_revs(self, wire, rev1, rev2, path2):
708 708 repo = self._factory.repo(wire)
709 709 LocalGitClient(thin_packs=False).fetch(path2, repo)
710 710
711 711 wire_remote = wire.copy()
712 712 wire_remote['path'] = path2
713 713 repo_remote = self._factory.repo(wire_remote)
714 714 LocalGitClient(thin_packs=False).fetch(wire["path"], repo_remote)
715 715
716 716 revs = [
717 717 x.commit.id
718 718 for x in repo_remote.get_walker(include=[rev2], exclude=[rev1])]
719 719 return revs
720 720
721 721 @reraise_safe_exceptions
722 722 def get_object(self, wire, sha, maybe_unreachable=False):
723 723 cache_on, context_uid, repo_id = self._cache_on(wire)
724 724 region = self._region(wire)
725 725 @region.conditional_cache_on_arguments(condition=cache_on)
726 726 def _get_object(_context_uid, _repo_id, _sha):
727 727 repo_init = self._factory.repo_libgit2(wire)
728 728 with repo_init as repo:
729 729
730 730 missing_commit_err = 'Commit {} does not exist for `{}`'.format(sha, wire['path'])
731 731 try:
732 732 commit = repo.revparse_single(sha)
733 733 except KeyError:
734 734 # NOTE(marcink): KeyError doesn't give us any meaningful information
735 735 # here, we instead give something more explicit
736 736 e = exceptions.RefNotFoundException('SHA: %s not found', sha)
737 737 raise exceptions.LookupException(e)(missing_commit_err)
738 738 except ValueError as e:
739 739 raise exceptions.LookupException(e)(missing_commit_err)
740 740
741 741 is_tag = False
742 742 if isinstance(commit, pygit2.Tag):
743 743 commit = repo.get(commit.target)
744 744 is_tag = True
745 745
746 746 check_dangling = True
747 747 if is_tag:
748 748 check_dangling = False
749 749
750 750 if check_dangling and maybe_unreachable:
751 751 check_dangling = False
752 752
753 753 # we used a reference and it parsed means we're not having a dangling commit
754 754 if sha != commit.hex:
755 755 check_dangling = False
756 756
757 757 if check_dangling:
758 758 # check for dangling commit
759 759 for branch in repo.branches.with_commit(commit.hex):
760 760 if branch:
761 761 break
762 762 else:
763 763 # NOTE(marcink): Empty error doesn't give us any meaningful information
764 764 # here, we instead give something more explicit
765 765 e = exceptions.RefNotFoundException('SHA: %s not found in branches', sha)
766 766 raise exceptions.LookupException(e)(missing_commit_err)
767 767
768 768 commit_id = commit.hex
769 769 type_id = commit.type
770 770
771 771 return {
772 772 'id': commit_id,
773 773 'type': self._type_id_to_name(type_id),
774 774 'commit_id': commit_id,
775 775 'idx': 0
776 776 }
777 777
778 778 return _get_object(context_uid, repo_id, sha)
779 779
780 780 @reraise_safe_exceptions
781 781 def get_refs(self, wire):
782 782 cache_on, context_uid, repo_id = self._cache_on(wire)
783 783 region = self._region(wire)
784 784 @region.conditional_cache_on_arguments(condition=cache_on)
785 785 def _get_refs(_context_uid, _repo_id):
786 786
787 787 repo_init = self._factory.repo_libgit2(wire)
788 788 with repo_init as repo:
789 789 regex = re.compile('^refs/(heads|tags)/')
790 790 return {x.name: x.target.hex for x in
791 791 filter(lambda ref: regex.match(ref.name) ,repo.listall_reference_objects())}
792 792
793 793 return _get_refs(context_uid, repo_id)
794 794
795 795 @reraise_safe_exceptions
796 796 def get_branch_pointers(self, wire):
797 797 cache_on, context_uid, repo_id = self._cache_on(wire)
798 798 region = self._region(wire)
799 799 @region.conditional_cache_on_arguments(condition=cache_on)
800 800 def _get_branch_pointers(_context_uid, _repo_id):
801 801
802 802 repo_init = self._factory.repo_libgit2(wire)
803 803 regex = re.compile('^refs/heads')
804 804 with repo_init as repo:
805 805 branches = filter(lambda ref: regex.match(ref.name), repo.listall_reference_objects())
806 806 return {x.target.hex: x.shorthand for x in branches}
807 807
808 808 return _get_branch_pointers(context_uid, repo_id)
809 809
810 810 @reraise_safe_exceptions
811 811 def head(self, wire, show_exc=True):
812 812 cache_on, context_uid, repo_id = self._cache_on(wire)
813 813 region = self._region(wire)
814 814 @region.conditional_cache_on_arguments(condition=cache_on)
815 815 def _head(_context_uid, _repo_id, _show_exc):
816 816 repo_init = self._factory.repo_libgit2(wire)
817 817 with repo_init as repo:
818 818 try:
819 819 return repo.head.peel().hex
820 820 except Exception:
821 821 if show_exc:
822 822 raise
823 823 return _head(context_uid, repo_id, show_exc)
824 824
825 825 @reraise_safe_exceptions
826 826 def init(self, wire):
827 827 repo_path = str_to_dulwich(wire['path'])
828 828 self.repo = Repo.init(repo_path)
829 829
830 830 @reraise_safe_exceptions
831 831 def init_bare(self, wire):
832 832 repo_path = str_to_dulwich(wire['path'])
833 833 self.repo = Repo.init_bare(repo_path)
834 834
835 835 @reraise_safe_exceptions
836 836 def revision(self, wire, rev):
837 837
838 838 cache_on, context_uid, repo_id = self._cache_on(wire)
839 839 region = self._region(wire)
840 840 @region.conditional_cache_on_arguments(condition=cache_on)
841 841 def _revision(_context_uid, _repo_id, _rev):
842 842 repo_init = self._factory.repo_libgit2(wire)
843 843 with repo_init as repo:
844 844 commit = repo[rev]
845 845 obj_data = {
846 846 'id': commit.id.hex,
847 847 }
848 848 # tree objects itself don't have tree_id attribute
849 849 if hasattr(commit, 'tree_id'):
850 850 obj_data['tree'] = commit.tree_id.hex
851 851
852 852 return obj_data
853 853 return _revision(context_uid, repo_id, rev)
854 854
855 855 @reraise_safe_exceptions
856 856 def date(self, wire, commit_id):
857 857 cache_on, context_uid, repo_id = self._cache_on(wire)
858 858 region = self._region(wire)
859 859 @region.conditional_cache_on_arguments(condition=cache_on)
860 860 def _date(_repo_id, _commit_id):
861 861 repo_init = self._factory.repo_libgit2(wire)
862 862 with repo_init as repo:
863 863 commit = repo[commit_id]
864 864
865 865 if hasattr(commit, 'commit_time'):
866 866 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
867 867 else:
868 868 commit = commit.get_object()
869 869 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
870 870
871 871 # TODO(marcink): check dulwich difference of offset vs timezone
872 872 return [commit_time, commit_time_offset]
873 873 return _date(repo_id, commit_id)
874 874
875 875 @reraise_safe_exceptions
876 876 def author(self, wire, commit_id):
877 877 cache_on, context_uid, repo_id = self._cache_on(wire)
878 878 region = self._region(wire)
879 879 @region.conditional_cache_on_arguments(condition=cache_on)
880 880 def _author(_repo_id, _commit_id):
881 881 repo_init = self._factory.repo_libgit2(wire)
882 882 with repo_init as repo:
883 883 commit = repo[commit_id]
884 884
885 885 if hasattr(commit, 'author'):
886 886 author = commit.author
887 887 else:
888 888 author = commit.get_object().author
889 889
890 890 if author.email:
891 891 return u"{} <{}>".format(author.name, author.email)
892 892
893 893 try:
894 894 return u"{}".format(author.name)
895 895 except Exception:
896 896 return u"{}".format(safe_unicode(author.raw_name))
897 897
898 898 return _author(repo_id, commit_id)
899 899
900 900 @reraise_safe_exceptions
901 901 def message(self, wire, commit_id):
902 902 cache_on, context_uid, repo_id = self._cache_on(wire)
903 903 region = self._region(wire)
904 904 @region.conditional_cache_on_arguments(condition=cache_on)
905 905 def _message(_repo_id, _commit_id):
906 906 repo_init = self._factory.repo_libgit2(wire)
907 907 with repo_init as repo:
908 908 commit = repo[commit_id]
909 909 return commit.message
910 910 return _message(repo_id, commit_id)
911 911
912 912 @reraise_safe_exceptions
913 913 def parents(self, wire, commit_id):
914 914 cache_on, context_uid, repo_id = self._cache_on(wire)
915 915 region = self._region(wire)
916 916 @region.conditional_cache_on_arguments(condition=cache_on)
917 917 def _parents(_repo_id, _commit_id):
918 918 repo_init = self._factory.repo_libgit2(wire)
919 919 with repo_init as repo:
920 920 commit = repo[commit_id]
921 921 if hasattr(commit, 'parent_ids'):
922 922 parent_ids = commit.parent_ids
923 923 else:
924 924 parent_ids = commit.get_object().parent_ids
925 925
926 926 return [x.hex for x in parent_ids]
927 927 return _parents(repo_id, commit_id)
928 928
929 929 @reraise_safe_exceptions
930 930 def children(self, wire, commit_id):
931 931 cache_on, context_uid, repo_id = self._cache_on(wire)
932 932 region = self._region(wire)
933 933 @region.conditional_cache_on_arguments(condition=cache_on)
934 934 def _children(_repo_id, _commit_id):
935 935 output, __ = self.run_git_command(
936 936 wire, ['rev-list', '--all', '--children'])
937 937
938 938 child_ids = []
939 939 pat = re.compile(r'^%s' % commit_id)
940 940 for l in output.splitlines():
941 941 if pat.match(l):
942 942 found_ids = l.split(' ')[1:]
943 943 child_ids.extend(found_ids)
944 944
945 945 return child_ids
946 946 return _children(repo_id, commit_id)
947 947
948 948 @reraise_safe_exceptions
949 949 def set_refs(self, wire, key, value):
950 950 repo_init = self._factory.repo_libgit2(wire)
951 951 with repo_init as repo:
952 952 repo.references.create(key, value, force=True)
953 953
954 954 @reraise_safe_exceptions
955 955 def create_branch(self, wire, branch_name, commit_id, force=False):
956 956 repo_init = self._factory.repo_libgit2(wire)
957 957 with repo_init as repo:
958 958 commit = repo[commit_id]
959 959
960 960 if force:
961 961 repo.branches.local.create(branch_name, commit, force=force)
962 962 elif not repo.branches.get(branch_name):
963 963 # create only if that branch isn't existing
964 964 repo.branches.local.create(branch_name, commit, force=force)
965 965
966 966 @reraise_safe_exceptions
967 967 def remove_ref(self, wire, key):
968 968 repo_init = self._factory.repo_libgit2(wire)
969 969 with repo_init as repo:
970 970 repo.references.delete(key)
971 971
972 972 @reraise_safe_exceptions
973 973 def tag_remove(self, wire, tag_name):
974 974 repo_init = self._factory.repo_libgit2(wire)
975 975 with repo_init as repo:
976 976 key = 'refs/tags/{}'.format(tag_name)
977 977 repo.references.delete(key)
978 978
979 979 @reraise_safe_exceptions
980 980 def tree_changes(self, wire, source_id, target_id):
981 981 # TODO(marcink): remove this seems it's only used by tests
982 982 repo = self._factory.repo(wire)
983 983 source = repo[source_id].tree if source_id else None
984 984 target = repo[target_id].tree
985 985 result = repo.object_store.tree_changes(source, target)
986 986 return list(result)
987 987
988 988 @reraise_safe_exceptions
989 989 def tree_and_type_for_path(self, wire, commit_id, path):
990 990
991 991 cache_on, context_uid, repo_id = self._cache_on(wire)
992 992 region = self._region(wire)
993 993 @region.conditional_cache_on_arguments(condition=cache_on)
994 994 def _tree_and_type_for_path(_context_uid, _repo_id, _commit_id, _path):
995 995 repo_init = self._factory.repo_libgit2(wire)
996 996
997 997 with repo_init as repo:
998 998 commit = repo[commit_id]
999 999 try:
1000 1000 tree = commit.tree[path]
1001 1001 except KeyError:
1002 1002 return None, None, None
1003 1003
1004 1004 return tree.id.hex, tree.type, tree.filemode
1005 1005 return _tree_and_type_for_path(context_uid, repo_id, commit_id, path)
1006 1006
1007 1007 @reraise_safe_exceptions
1008 1008 def tree_items(self, wire, tree_id):
1009 1009 cache_on, context_uid, repo_id = self._cache_on(wire)
1010 1010 region = self._region(wire)
1011 1011 @region.conditional_cache_on_arguments(condition=cache_on)
1012 1012 def _tree_items(_repo_id, _tree_id):
1013 1013
1014 1014 repo_init = self._factory.repo_libgit2(wire)
1015 1015 with repo_init as repo:
1016 1016 try:
1017 1017 tree = repo[tree_id]
1018 1018 except KeyError:
1019 1019 raise ObjectMissing('No tree with id: {}'.format(tree_id))
1020 1020
1021 1021 result = []
1022 1022 for item in tree:
1023 1023 item_sha = item.hex
1024 1024 item_mode = item.filemode
1025 1025 item_type = item.type
1026 1026
1027 1027 if item_type == 'commit':
1028 1028 # NOTE(marcink): submodules we translate to 'link' for backward compat
1029 1029 item_type = 'link'
1030 1030
1031 1031 result.append((item.name, item_mode, item_sha, item_type))
1032 1032 return result
1033 1033 return _tree_items(repo_id, tree_id)
1034 1034
1035 1035 @reraise_safe_exceptions
1036 1036 def diff_2(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1037 1037 """
1038 1038 Old version that uses subprocess to call diff
1039 1039 """
1040 1040
1041 1041 flags = [
1042 1042 '-U%s' % context, '--patch',
1043 1043 '--binary',
1044 1044 '--find-renames',
1045 1045 '--no-indent-heuristic',
1046 1046 # '--indent-heuristic',
1047 1047 #'--full-index',
1048 1048 #'--abbrev=40'
1049 1049 ]
1050 1050
1051 1051 if opt_ignorews:
1052 1052 flags.append('--ignore-all-space')
1053 1053
1054 1054 if commit_id_1 == self.EMPTY_COMMIT:
1055 1055 cmd = ['show'] + flags + [commit_id_2]
1056 1056 else:
1057 1057 cmd = ['diff'] + flags + [commit_id_1, commit_id_2]
1058 1058
1059 1059 if file_filter:
1060 1060 cmd.extend(['--', file_filter])
1061 1061
1062 1062 diff, __ = self.run_git_command(wire, cmd)
1063 1063 # If we used 'show' command, strip first few lines (until actual diff
1064 1064 # starts)
1065 1065 if commit_id_1 == self.EMPTY_COMMIT:
1066 1066 lines = diff.splitlines()
1067 1067 x = 0
1068 1068 for line in lines:
1069 1069 if line.startswith('diff'):
1070 1070 break
1071 1071 x += 1
1072 1072 # Append new line just like 'diff' command do
1073 1073 diff = '\n'.join(lines[x:]) + '\n'
1074 1074 return diff
1075 1075
1076 1076 @reraise_safe_exceptions
1077 1077 def diff(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1078 1078 repo_init = self._factory.repo_libgit2(wire)
1079 1079 with repo_init as repo:
1080 1080 swap = True
1081 1081 flags = 0
1082 1082 flags |= pygit2.GIT_DIFF_SHOW_BINARY
1083 1083
1084 1084 if opt_ignorews:
1085 1085 flags |= pygit2.GIT_DIFF_IGNORE_WHITESPACE
1086 1086
1087 1087 if commit_id_1 == self.EMPTY_COMMIT:
1088 1088 comm1 = repo[commit_id_2]
1089 1089 diff_obj = comm1.tree.diff_to_tree(
1090 1090 flags=flags, context_lines=context, swap=swap)
1091 1091
1092 1092 else:
1093 1093 comm1 = repo[commit_id_2]
1094 1094 comm2 = repo[commit_id_1]
1095 1095 diff_obj = comm1.tree.diff_to_tree(
1096 1096 comm2.tree, flags=flags, context_lines=context, swap=swap)
1097 1097 similar_flags = 0
1098 1098 similar_flags |= pygit2.GIT_DIFF_FIND_RENAMES
1099 1099 diff_obj.find_similar(flags=similar_flags)
1100 1100
1101 1101 if file_filter:
1102 1102 for p in diff_obj:
1103 1103 if p.delta.old_file.path == file_filter:
1104 1104 return p.patch or ''
1105 1105 # fo matching path == no diff
1106 1106 return ''
1107 1107 return diff_obj.patch or ''
1108 1108
1109 1109 @reraise_safe_exceptions
1110 1110 def node_history(self, wire, commit_id, path, limit):
1111 1111 cache_on, context_uid, repo_id = self._cache_on(wire)
1112 1112 region = self._region(wire)
1113 1113 @region.conditional_cache_on_arguments(condition=cache_on)
1114 1114 def _node_history(_context_uid, _repo_id, _commit_id, _path, _limit):
1115 1115 # optimize for n==1, rev-list is much faster for that use-case
1116 1116 if limit == 1:
1117 1117 cmd = ['rev-list', '-1', commit_id, '--', path]
1118 1118 else:
1119 1119 cmd = ['log']
1120 1120 if limit:
1121 1121 cmd.extend(['-n', str(safe_int(limit, 0))])
1122 1122 cmd.extend(['--pretty=format: %H', '-s', commit_id, '--', path])
1123 1123
1124 1124 output, __ = self.run_git_command(wire, cmd)
1125 1125 commit_ids = re.findall(r'[0-9a-fA-F]{40}', output)
1126 1126
1127 1127 return [x for x in commit_ids]
1128 1128 return _node_history(context_uid, repo_id, commit_id, path, limit)
1129 1129
1130 1130 @reraise_safe_exceptions
1131 1131 def node_annotate(self, wire, commit_id, path):
1132 1132
1133 1133 cmd = ['blame', '-l', '--root', '-r', commit_id, '--', path]
1134 1134 # -l ==> outputs long shas (and we need all 40 characters)
1135 1135 # --root ==> doesn't put '^' character for boundaries
1136 1136 # -r commit_id ==> blames for the given commit
1137 1137 output, __ = self.run_git_command(wire, cmd)
1138 1138
1139 1139 result = []
1140 1140 for i, blame_line in enumerate(output.split('\n')[:-1]):
1141 1141 line_no = i + 1
1142 1142 commit_id, line = re.split(r' ', blame_line, 1)
1143 1143 result.append((line_no, commit_id, line))
1144 1144 return result
1145 1145
1146 1146 @reraise_safe_exceptions
1147 1147 def update_server_info(self, wire):
1148 1148 repo = self._factory.repo(wire)
1149 1149 update_server_info(repo)
1150 1150
1151 1151 @reraise_safe_exceptions
1152 1152 def get_all_commit_ids(self, wire):
1153 1153
1154 1154 cache_on, context_uid, repo_id = self._cache_on(wire)
1155 1155 region = self._region(wire)
1156 1156 @region.conditional_cache_on_arguments(condition=cache_on)
1157 1157 def _get_all_commit_ids(_context_uid, _repo_id):
1158 1158
1159 1159 cmd = ['rev-list', '--reverse', '--date-order', '--branches', '--tags']
1160 1160 try:
1161 1161 output, __ = self.run_git_command(wire, cmd)
1162 1162 return output.splitlines()
1163 1163 except Exception:
1164 1164 # Can be raised for empty repositories
1165 1165 return []
1166 1166 return _get_all_commit_ids(context_uid, repo_id)
1167 1167
1168 1168 @reraise_safe_exceptions
1169 1169 def run_git_command(self, wire, cmd, **opts):
1170 1170 path = wire.get('path', None)
1171 1171
1172 1172 if path and os.path.isdir(path):
1173 1173 opts['cwd'] = path
1174 1174
1175 1175 if '_bare' in opts:
1176 1176 _copts = []
1177 1177 del opts['_bare']
1178 1178 else:
1179 1179 _copts = ['-c', 'core.quotepath=false', ]
1180 1180 safe_call = False
1181 1181 if '_safe' in opts:
1182 1182 # no exc on failure
1183 1183 del opts['_safe']
1184 1184 safe_call = True
1185 1185
1186 1186 if '_copts' in opts:
1187 1187 _copts.extend(opts['_copts'] or [])
1188 1188 del opts['_copts']
1189 1189
1190 1190 gitenv = os.environ.copy()
1191 1191 gitenv.update(opts.pop('extra_env', {}))
1192 1192 # need to clean fix GIT_DIR !
1193 1193 if 'GIT_DIR' in gitenv:
1194 1194 del gitenv['GIT_DIR']
1195 1195 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
1196 1196 gitenv['GIT_DISCOVERY_ACROSS_FILESYSTEM'] = '1'
1197 1197
1198 1198 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
1199 1199 _opts = {'env': gitenv, 'shell': False}
1200 1200
1201 1201 proc = None
1202 1202 try:
1203 1203 _opts.update(opts)
1204 1204 proc = subprocessio.SubprocessIOChunker(cmd, **_opts)
1205 1205
1206 1206 return ''.join(proc), ''.join(proc.error)
1207 1207 except (EnvironmentError, OSError) as err:
1208 1208 cmd = ' '.join(cmd) # human friendly CMD
1209 1209 tb_err = ("Couldn't run git command (%s).\n"
1210 1210 "Original error was:%s\n"
1211 1211 "Call options:%s\n"
1212 1212 % (cmd, err, _opts))
1213 1213 log.exception(tb_err)
1214 1214 if safe_call:
1215 1215 return '', err
1216 1216 else:
1217 1217 raise exceptions.VcsException()(tb_err)
1218 1218 finally:
1219 1219 if proc:
1220 1220 proc.close()
1221 1221
1222 1222 @reraise_safe_exceptions
1223 1223 def install_hooks(self, wire, force=False):
1224 1224 from vcsserver.hook_utils import install_git_hooks
1225 1225 bare = self.bare(wire)
1226 1226 path = wire['path']
1227 1227 return install_git_hooks(path, bare, force_create=force)
1228 1228
1229 1229 @reraise_safe_exceptions
1230 1230 def get_hooks_info(self, wire):
1231 1231 from vcsserver.hook_utils import (
1232 1232 get_git_pre_hook_version, get_git_post_hook_version)
1233 1233 bare = self.bare(wire)
1234 1234 path = wire['path']
1235 1235 return {
1236 1236 'pre_version': get_git_pre_hook_version(path, bare),
1237 1237 'post_version': get_git_post_hook_version(path, bare),
1238 1238 }
1239 1239
1240 1240 @reraise_safe_exceptions
1241 1241 def set_head_ref(self, wire, head_name):
1242 1242 log.debug('Setting refs/head to `%s`', head_name)
1243 cmd = ['symbolic-ref', 'HEAD', 'refs/heads/%s' % head_name]
1243 cmd = ['symbolic-ref', '"HEAD"', '"refs/heads/%s"' % head_name]
1244 1244 output, __ = self.run_git_command(wire, cmd)
1245 1245 return [head_name] + output.splitlines()
1246 1246
1247 1247 @reraise_safe_exceptions
1248 1248 def archive_repo(self, wire, archive_dest_path, kind, mtime, archive_at_path,
1249 1249 archive_dir_name, commit_id):
1250 1250
1251 1251 def file_walker(_commit_id, path):
1252 1252 repo_init = self._factory.repo_libgit2(wire)
1253 1253
1254 1254 with repo_init as repo:
1255 1255 commit = repo[commit_id]
1256 1256
1257 1257 if path in ['', '/']:
1258 1258 tree = commit.tree
1259 1259 else:
1260 1260 tree = commit.tree[path.rstrip('/')]
1261 1261 tree_id = tree.id.hex
1262 1262 try:
1263 1263 tree = repo[tree_id]
1264 1264 except KeyError:
1265 1265 raise ObjectMissing('No tree with id: {}'.format(tree_id))
1266 1266
1267 1267 index = LibGit2Index.Index()
1268 1268 index.read_tree(tree)
1269 1269 file_iter = index
1270 1270
1271 1271 for fn in file_iter:
1272 1272 file_path = fn.path
1273 1273 mode = fn.mode
1274 1274 is_link = stat.S_ISLNK(mode)
1275 1275 if mode == pygit2.GIT_FILEMODE_COMMIT:
1276 1276 log.debug('Skipping path %s as a commit node', file_path)
1277 1277 continue
1278 1278 yield ArchiveNode(file_path, mode, is_link, repo[fn.hex].read_raw)
1279 1279
1280 1280 return archive_repo(file_walker, archive_dest_path, kind, mtime, archive_at_path,
1281 1281 archive_dir_name, commit_id)
@@ -1,729 +1,729 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # RhodeCode VCSServer provides access to different vcs backends via network.
4 4 # Copyright (C) 2014-2020 RhodeCode GmbH
5 5 #
6 6 # This program is free software; you can redistribute it and/or modify
7 7 # it under the terms of the GNU General Public License as published by
8 8 # the Free Software Foundation; either version 3 of the License, or
9 9 # (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software Foundation,
18 18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 19
20 20 import io
21 21 import os
22 22 import sys
23 23 import logging
24 24 import collections
25 25 import importlib
26 26 import base64
27 27
28 28 from httplib import HTTPConnection
29 29
30 30
31 31 import mercurial.scmutil
32 32 import mercurial.node
33 33 import simplejson as json
34 34
35 35 from vcsserver import exceptions, subprocessio, settings
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 class HooksHttpClient(object):
41 41 connection = None
42 42
43 43 def __init__(self, hooks_uri):
44 44 self.hooks_uri = hooks_uri
45 45
46 46 def __call__(self, method, extras):
47 47 connection = HTTPConnection(self.hooks_uri)
48 48 body = self._serialize(method, extras)
49 49 try:
50 50 connection.request('POST', '/', body)
51 51 except Exception:
52 52 log.error('Hooks calling Connection failed on %s', connection.__dict__)
53 53 raise
54 54 response = connection.getresponse()
55 55
56 56 response_data = response.read()
57 57
58 58 try:
59 59 return json.loads(response_data)
60 60 except Exception:
61 61 log.exception('Failed to decode hook response json data. '
62 62 'response_code:%s, raw_data:%s',
63 63 response.status, response_data)
64 64 raise
65 65
66 66 def _serialize(self, hook_name, extras):
67 67 data = {
68 68 'method': hook_name,
69 69 'extras': extras
70 70 }
71 71 return json.dumps(data)
72 72
73 73
74 74 class HooksDummyClient(object):
75 75 def __init__(self, hooks_module):
76 76 self._hooks_module = importlib.import_module(hooks_module)
77 77
78 78 def __call__(self, hook_name, extras):
79 79 with self._hooks_module.Hooks() as hooks:
80 80 return getattr(hooks, hook_name)(extras)
81 81
82 82
83 83 class HooksShadowRepoClient(object):
84 84
85 85 def __call__(self, hook_name, extras):
86 86 return {'output': '', 'status': 0}
87 87
88 88
89 89 class RemoteMessageWriter(object):
90 90 """Writer base class."""
91 91 def write(self, message):
92 92 raise NotImplementedError()
93 93
94 94
95 95 class HgMessageWriter(RemoteMessageWriter):
96 96 """Writer that knows how to send messages to mercurial clients."""
97 97
98 98 def __init__(self, ui):
99 99 self.ui = ui
100 100
101 101 def write(self, message):
102 102 # TODO: Check why the quiet flag is set by default.
103 103 old = self.ui.quiet
104 104 self.ui.quiet = False
105 105 self.ui.status(message.encode('utf-8'))
106 106 self.ui.quiet = old
107 107
108 108
109 109 class GitMessageWriter(RemoteMessageWriter):
110 110 """Writer that knows how to send messages to git clients."""
111 111
112 112 def __init__(self, stdout=None):
113 113 self.stdout = stdout or sys.stdout
114 114
115 115 def write(self, message):
116 116 self.stdout.write(message.encode('utf-8'))
117 117
118 118
119 119 class SvnMessageWriter(RemoteMessageWriter):
120 120 """Writer that knows how to send messages to svn clients."""
121 121
122 122 def __init__(self, stderr=None):
123 123 # SVN needs data sent to stderr for back-to-client messaging
124 124 self.stderr = stderr or sys.stderr
125 125
126 126 def write(self, message):
127 127 self.stderr.write(message.encode('utf-8'))
128 128
129 129
130 130 def _handle_exception(result):
131 131 exception_class = result.get('exception')
132 132 exception_traceback = result.get('exception_traceback')
133 133
134 134 if exception_traceback:
135 135 log.error('Got traceback from remote call:%s', exception_traceback)
136 136
137 137 if exception_class == 'HTTPLockedRC':
138 138 raise exceptions.RepositoryLockedException()(*result['exception_args'])
139 139 elif exception_class == 'HTTPBranchProtected':
140 140 raise exceptions.RepositoryBranchProtectedException()(*result['exception_args'])
141 141 elif exception_class == 'RepositoryError':
142 142 raise exceptions.VcsException()(*result['exception_args'])
143 143 elif exception_class:
144 144 raise Exception('Got remote exception "%s" with args "%s"' %
145 145 (exception_class, result['exception_args']))
146 146
147 147
148 148 def _get_hooks_client(extras):
149 149 hooks_uri = extras.get('hooks_uri')
150 150 is_shadow_repo = extras.get('is_shadow_repo')
151 151 if hooks_uri:
152 152 return HooksHttpClient(extras['hooks_uri'])
153 153 elif is_shadow_repo:
154 154 return HooksShadowRepoClient()
155 155 else:
156 156 return HooksDummyClient(extras['hooks_module'])
157 157
158 158
159 159 def _call_hook(hook_name, extras, writer):
160 160 hooks_client = _get_hooks_client(extras)
161 161 log.debug('Hooks, using client:%s', hooks_client)
162 162 result = hooks_client(hook_name, extras)
163 163 log.debug('Hooks got result: %s', result)
164 164
165 165 _handle_exception(result)
166 166 writer.write(result['output'])
167 167
168 168 return result['status']
169 169
170 170
171 171 def _extras_from_ui(ui):
172 172 hook_data = ui.config('rhodecode', 'RC_SCM_DATA')
173 173 if not hook_data:
174 174 # maybe it's inside environ ?
175 175 env_hook_data = os.environ.get('RC_SCM_DATA')
176 176 if env_hook_data:
177 177 hook_data = env_hook_data
178 178
179 179 extras = {}
180 180 if hook_data:
181 181 extras = json.loads(hook_data)
182 182 return extras
183 183
184 184
185 185 def _rev_range_hash(repo, node, check_heads=False):
186 186 from vcsserver.hgcompat import get_ctx
187 187
188 188 commits = []
189 189 revs = []
190 190 start = get_ctx(repo, node).rev()
191 191 end = len(repo)
192 192 for rev in range(start, end):
193 193 revs.append(rev)
194 194 ctx = get_ctx(repo, rev)
195 195 commit_id = mercurial.node.hex(ctx.node())
196 196 branch = ctx.branch()
197 197 commits.append((commit_id, branch))
198 198
199 199 parent_heads = []
200 200 if check_heads:
201 201 parent_heads = _check_heads(repo, start, end, revs)
202 202 return commits, parent_heads
203 203
204 204
205 205 def _check_heads(repo, start, end, commits):
206 206 from vcsserver.hgcompat import get_ctx
207 207 changelog = repo.changelog
208 208 parents = set()
209 209
210 210 for new_rev in commits:
211 211 for p in changelog.parentrevs(new_rev):
212 212 if p == mercurial.node.nullrev:
213 213 continue
214 214 if p < start:
215 215 parents.add(p)
216 216
217 217 for p in parents:
218 218 branch = get_ctx(repo, p).branch()
219 219 # The heads descending from that parent, on the same branch
220 220 parent_heads = set([p])
221 221 reachable = set([p])
222 222 for x in xrange(p + 1, end):
223 223 if get_ctx(repo, x).branch() != branch:
224 224 continue
225 225 for pp in changelog.parentrevs(x):
226 226 if pp in reachable:
227 227 reachable.add(x)
228 228 parent_heads.discard(pp)
229 229 parent_heads.add(x)
230 230 # More than one head? Suggest merging
231 231 if len(parent_heads) > 1:
232 232 return list(parent_heads)
233 233
234 234 return []
235 235
236 236
237 237 def _get_git_env():
238 238 env = {}
239 239 for k, v in os.environ.items():
240 240 if k.startswith('GIT'):
241 241 env[k] = v
242 242
243 243 # serialized version
244 244 return [(k, v) for k, v in env.items()]
245 245
246 246
247 247 def _get_hg_env(old_rev, new_rev, txnid, repo_path):
248 248 env = {}
249 249 for k, v in os.environ.items():
250 250 if k.startswith('HG'):
251 251 env[k] = v
252 252
253 253 env['HG_NODE'] = old_rev
254 254 env['HG_NODE_LAST'] = new_rev
255 255 env['HG_TXNID'] = txnid
256 256 env['HG_PENDING'] = repo_path
257 257
258 258 return [(k, v) for k, v in env.items()]
259 259
260 260
261 261 def repo_size(ui, repo, **kwargs):
262 262 extras = _extras_from_ui(ui)
263 263 return _call_hook('repo_size', extras, HgMessageWriter(ui))
264 264
265 265
266 266 def pre_pull(ui, repo, **kwargs):
267 267 extras = _extras_from_ui(ui)
268 268 return _call_hook('pre_pull', extras, HgMessageWriter(ui))
269 269
270 270
271 271 def pre_pull_ssh(ui, repo, **kwargs):
272 272 extras = _extras_from_ui(ui)
273 273 if extras and extras.get('SSH'):
274 274 return pre_pull(ui, repo, **kwargs)
275 275 return 0
276 276
277 277
278 278 def post_pull(ui, repo, **kwargs):
279 279 extras = _extras_from_ui(ui)
280 280 return _call_hook('post_pull', extras, HgMessageWriter(ui))
281 281
282 282
283 283 def post_pull_ssh(ui, repo, **kwargs):
284 284 extras = _extras_from_ui(ui)
285 285 if extras and extras.get('SSH'):
286 286 return post_pull(ui, repo, **kwargs)
287 287 return 0
288 288
289 289
290 290 def pre_push(ui, repo, node=None, **kwargs):
291 291 """
292 292 Mercurial pre_push hook
293 293 """
294 294 extras = _extras_from_ui(ui)
295 295 detect_force_push = extras.get('detect_force_push')
296 296
297 297 rev_data = []
298 298 if node and kwargs.get('hooktype') == 'pretxnchangegroup':
299 299 branches = collections.defaultdict(list)
300 300 commits, _heads = _rev_range_hash(repo, node, check_heads=detect_force_push)
301 301 for commit_id, branch in commits:
302 302 branches[branch].append(commit_id)
303 303
304 304 for branch, commits in branches.items():
305 305 old_rev = kwargs.get('node_last') or commits[0]
306 306 rev_data.append({
307 307 'total_commits': len(commits),
308 308 'old_rev': old_rev,
309 309 'new_rev': commits[-1],
310 310 'ref': '',
311 311 'type': 'branch',
312 312 'name': branch,
313 313 })
314 314
315 315 for push_ref in rev_data:
316 316 push_ref['multiple_heads'] = _heads
317 317
318 318 repo_path = os.path.join(
319 319 extras.get('repo_store', ''), extras.get('repository', ''))
320 320 push_ref['hg_env'] = _get_hg_env(
321 321 old_rev=push_ref['old_rev'],
322 322 new_rev=push_ref['new_rev'], txnid=kwargs.get('txnid'),
323 323 repo_path=repo_path)
324 324
325 325 extras['hook_type'] = kwargs.get('hooktype', 'pre_push')
326 326 extras['commit_ids'] = rev_data
327 327
328 328 return _call_hook('pre_push', extras, HgMessageWriter(ui))
329 329
330 330
331 331 def pre_push_ssh(ui, repo, node=None, **kwargs):
332 332 extras = _extras_from_ui(ui)
333 333 if extras.get('SSH'):
334 334 return pre_push(ui, repo, node, **kwargs)
335 335
336 336 return 0
337 337
338 338
339 339 def pre_push_ssh_auth(ui, repo, node=None, **kwargs):
340 340 """
341 341 Mercurial pre_push hook for SSH
342 342 """
343 343 extras = _extras_from_ui(ui)
344 344 if extras.get('SSH'):
345 345 permission = extras['SSH_PERMISSIONS']
346 346
347 347 if 'repository.write' == permission or 'repository.admin' == permission:
348 348 return 0
349 349
350 350 # non-zero ret code
351 351 return 1
352 352
353 353 return 0
354 354
355 355
356 356 def post_push(ui, repo, node, **kwargs):
357 357 """
358 358 Mercurial post_push hook
359 359 """
360 360 extras = _extras_from_ui(ui)
361 361
362 362 commit_ids = []
363 363 branches = []
364 364 bookmarks = []
365 365 tags = []
366 366
367 367 commits, _heads = _rev_range_hash(repo, node)
368 368 for commit_id, branch in commits:
369 369 commit_ids.append(commit_id)
370 370 if branch not in branches:
371 371 branches.append(branch)
372 372
373 373 if hasattr(ui, '_rc_pushkey_branches'):
374 374 bookmarks = ui._rc_pushkey_branches
375 375
376 376 extras['hook_type'] = kwargs.get('hooktype', 'post_push')
377 377 extras['commit_ids'] = commit_ids
378 378 extras['new_refs'] = {
379 379 'branches': branches,
380 380 'bookmarks': bookmarks,
381 381 'tags': tags
382 382 }
383 383
384 384 return _call_hook('post_push', extras, HgMessageWriter(ui))
385 385
386 386
387 387 def post_push_ssh(ui, repo, node, **kwargs):
388 388 """
389 389 Mercurial post_push hook for SSH
390 390 """
391 391 if _extras_from_ui(ui).get('SSH'):
392 392 return post_push(ui, repo, node, **kwargs)
393 393 return 0
394 394
395 395
396 396 def key_push(ui, repo, **kwargs):
397 397 from vcsserver.hgcompat import get_ctx
398 398 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
399 399 # store new bookmarks in our UI object propagated later to post_push
400 400 ui._rc_pushkey_branches = get_ctx(repo, kwargs['key']).bookmarks()
401 401 return
402 402
403 403
404 404 # backward compat
405 405 log_pull_action = post_pull
406 406
407 407 # backward compat
408 408 log_push_action = post_push
409 409
410 410
411 411 def handle_git_pre_receive(unused_repo_path, unused_revs, unused_env):
412 412 """
413 413 Old hook name: keep here for backward compatibility.
414 414
415 415 This is only required when the installed git hooks are not upgraded.
416 416 """
417 417 pass
418 418
419 419
420 420 def handle_git_post_receive(unused_repo_path, unused_revs, unused_env):
421 421 """
422 422 Old hook name: keep here for backward compatibility.
423 423
424 424 This is only required when the installed git hooks are not upgraded.
425 425 """
426 426 pass
427 427
428 428
429 429 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
430 430
431 431
432 432 def git_pre_pull(extras):
433 433 """
434 434 Pre pull hook.
435 435
436 436 :param extras: dictionary containing the keys defined in simplevcs
437 437 :type extras: dict
438 438
439 439 :return: status code of the hook. 0 for success.
440 440 :rtype: int
441 441 """
442 442 if 'pull' not in extras['hooks']:
443 443 return HookResponse(0, '')
444 444
445 445 stdout = io.BytesIO()
446 446 try:
447 447 status = _call_hook('pre_pull', extras, GitMessageWriter(stdout))
448 448 except Exception as error:
449 449 status = 128
450 450 stdout.write('ERROR: %s\n' % str(error))
451 451
452 452 return HookResponse(status, stdout.getvalue())
453 453
454 454
455 455 def git_post_pull(extras):
456 456 """
457 457 Post pull hook.
458 458
459 459 :param extras: dictionary containing the keys defined in simplevcs
460 460 :type extras: dict
461 461
462 462 :return: status code of the hook. 0 for success.
463 463 :rtype: int
464 464 """
465 465 if 'pull' not in extras['hooks']:
466 466 return HookResponse(0, '')
467 467
468 468 stdout = io.BytesIO()
469 469 try:
470 470 status = _call_hook('post_pull', extras, GitMessageWriter(stdout))
471 471 except Exception as error:
472 472 status = 128
473 473 stdout.write('ERROR: %s\n' % error)
474 474
475 475 return HookResponse(status, stdout.getvalue())
476 476
477 477
478 478 def _parse_git_ref_lines(revision_lines):
479 479 rev_data = []
480 480 for revision_line in revision_lines or []:
481 481 old_rev, new_rev, ref = revision_line.strip().split(' ')
482 482 ref_data = ref.split('/', 2)
483 483 if ref_data[1] in ('tags', 'heads'):
484 484 rev_data.append({
485 485 # NOTE(marcink):
486 486 # we're unable to tell total_commits for git at this point
487 487 # but we set the variable for consistency with GIT
488 488 'total_commits': -1,
489 489 'old_rev': old_rev,
490 490 'new_rev': new_rev,
491 491 'ref': ref,
492 492 'type': ref_data[1],
493 493 'name': ref_data[2],
494 494 })
495 495 return rev_data
496 496
497 497
498 498 def git_pre_receive(unused_repo_path, revision_lines, env):
499 499 """
500 500 Pre push hook.
501 501
502 502 :param extras: dictionary containing the keys defined in simplevcs
503 503 :type extras: dict
504 504
505 505 :return: status code of the hook. 0 for success.
506 506 :rtype: int
507 507 """
508 508 extras = json.loads(env['RC_SCM_DATA'])
509 509 rev_data = _parse_git_ref_lines(revision_lines)
510 510 if 'push' not in extras['hooks']:
511 511 return 0
512 512 empty_commit_id = '0' * 40
513 513
514 514 detect_force_push = extras.get('detect_force_push')
515 515
516 516 for push_ref in rev_data:
517 517 # store our git-env which holds the temp store
518 518 push_ref['git_env'] = _get_git_env()
519 519 push_ref['pruned_sha'] = ''
520 520 if not detect_force_push:
521 521 # don't check for forced-push when we don't need to
522 522 continue
523 523
524 524 type_ = push_ref['type']
525 525 new_branch = push_ref['old_rev'] == empty_commit_id
526 526 delete_branch = push_ref['new_rev'] == empty_commit_id
527 527 if type_ == 'heads' and not (new_branch or delete_branch):
528 528 old_rev = push_ref['old_rev']
529 529 new_rev = push_ref['new_rev']
530 530 cmd = [settings.GIT_EXECUTABLE, 'rev-list', old_rev, '^{}'.format(new_rev)]
531 531 stdout, stderr = subprocessio.run_command(
532 532 cmd, env=os.environ.copy())
533 533 # means we're having some non-reachable objects, this forced push was used
534 534 if stdout:
535 535 push_ref['pruned_sha'] = stdout.splitlines()
536 536
537 537 extras['hook_type'] = 'pre_receive'
538 538 extras['commit_ids'] = rev_data
539 539 return _call_hook('pre_push', extras, GitMessageWriter())
540 540
541 541
542 542 def git_post_receive(unused_repo_path, revision_lines, env):
543 543 """
544 544 Post push hook.
545 545
546 546 :param extras: dictionary containing the keys defined in simplevcs
547 547 :type extras: dict
548 548
549 549 :return: status code of the hook. 0 for success.
550 550 :rtype: int
551 551 """
552 552 extras = json.loads(env['RC_SCM_DATA'])
553 553 if 'push' not in extras['hooks']:
554 554 return 0
555 555
556 556 rev_data = _parse_git_ref_lines(revision_lines)
557 557
558 558 git_revs = []
559 559
560 560 # N.B.(skreft): it is ok to just call git, as git before calling a
561 561 # subcommand sets the PATH environment variable so that it point to the
562 562 # correct version of the git executable.
563 563 empty_commit_id = '0' * 40
564 564 branches = []
565 565 tags = []
566 566 for push_ref in rev_data:
567 567 type_ = push_ref['type']
568 568
569 569 if type_ == 'heads':
570 570 if push_ref['old_rev'] == empty_commit_id:
571 571 # starting new branch case
572 572 if push_ref['name'] not in branches:
573 573 branches.append(push_ref['name'])
574 574
575 575 # Fix up head revision if needed
576 576 cmd = [settings.GIT_EXECUTABLE, 'show', 'HEAD']
577 577 try:
578 578 subprocessio.run_command(cmd, env=os.environ.copy())
579 579 except Exception:
580 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', 'HEAD',
581 'refs/heads/%s' % push_ref['name']]
580 cmd = [settings.GIT_EXECUTABLE, 'symbolic-ref', '"HEAD"',
581 '"refs/heads/%s"' % push_ref['name']]
582 582 print("Setting default branch to %s" % push_ref['name'])
583 583 subprocessio.run_command(cmd, env=os.environ.copy())
584 584
585 585 cmd = [settings.GIT_EXECUTABLE, 'for-each-ref',
586 586 '--format=%(refname)', 'refs/heads/*']
587 587 stdout, stderr = subprocessio.run_command(
588 588 cmd, env=os.environ.copy())
589 589 heads = stdout
590 590 heads = heads.replace(push_ref['ref'], '')
591 591 heads = ' '.join(head for head
592 592 in heads.splitlines() if head) or '.'
593 593 cmd = [settings.GIT_EXECUTABLE, 'log', '--reverse',
594 594 '--pretty=format:%H', '--', push_ref['new_rev'],
595 595 '--not', heads]
596 596 stdout, stderr = subprocessio.run_command(
597 597 cmd, env=os.environ.copy())
598 598 git_revs.extend(stdout.splitlines())
599 599 elif push_ref['new_rev'] == empty_commit_id:
600 600 # delete branch case
601 601 git_revs.append('delete_branch=>%s' % push_ref['name'])
602 602 else:
603 603 if push_ref['name'] not in branches:
604 604 branches.append(push_ref['name'])
605 605
606 606 cmd = [settings.GIT_EXECUTABLE, 'log',
607 607 '{old_rev}..{new_rev}'.format(**push_ref),
608 608 '--reverse', '--pretty=format:%H']
609 609 stdout, stderr = subprocessio.run_command(
610 610 cmd, env=os.environ.copy())
611 611 git_revs.extend(stdout.splitlines())
612 612 elif type_ == 'tags':
613 613 if push_ref['name'] not in tags:
614 614 tags.append(push_ref['name'])
615 615 git_revs.append('tag=>%s' % push_ref['name'])
616 616
617 617 extras['hook_type'] = 'post_receive'
618 618 extras['commit_ids'] = git_revs
619 619 extras['new_refs'] = {
620 620 'branches': branches,
621 621 'bookmarks': [],
622 622 'tags': tags,
623 623 }
624 624
625 625 if 'repo_size' in extras['hooks']:
626 626 try:
627 627 _call_hook('repo_size', extras, GitMessageWriter())
628 628 except:
629 629 pass
630 630
631 631 return _call_hook('post_push', extras, GitMessageWriter())
632 632
633 633
634 634 def _get_extras_from_txn_id(path, txn_id):
635 635 extras = {}
636 636 try:
637 637 cmd = [settings.SVNLOOK_EXECUTABLE, 'pget',
638 638 '-t', txn_id,
639 639 '--revprop', path, 'rc-scm-extras']
640 640 stdout, stderr = subprocessio.run_command(
641 641 cmd, env=os.environ.copy())
642 642 extras = json.loads(base64.urlsafe_b64decode(stdout))
643 643 except Exception:
644 644 log.exception('Failed to extract extras info from txn_id')
645 645
646 646 return extras
647 647
648 648
649 649 def _get_extras_from_commit_id(commit_id, path):
650 650 extras = {}
651 651 try:
652 652 cmd = [settings.SVNLOOK_EXECUTABLE, 'pget',
653 653 '-r', commit_id,
654 654 '--revprop', path, 'rc-scm-extras']
655 655 stdout, stderr = subprocessio.run_command(
656 656 cmd, env=os.environ.copy())
657 657 extras = json.loads(base64.urlsafe_b64decode(stdout))
658 658 except Exception:
659 659 log.exception('Failed to extract extras info from commit_id')
660 660
661 661 return extras
662 662
663 663
664 664 def svn_pre_commit(repo_path, commit_data, env):
665 665 path, txn_id = commit_data
666 666 branches = []
667 667 tags = []
668 668
669 669 if env.get('RC_SCM_DATA'):
670 670 extras = json.loads(env['RC_SCM_DATA'])
671 671 else:
672 672 # fallback method to read from TXN-ID stored data
673 673 extras = _get_extras_from_txn_id(path, txn_id)
674 674 if not extras:
675 675 return 0
676 676
677 677 extras['hook_type'] = 'pre_commit'
678 678 extras['commit_ids'] = [txn_id]
679 679 extras['txn_id'] = txn_id
680 680 extras['new_refs'] = {
681 681 'total_commits': 1,
682 682 'branches': branches,
683 683 'bookmarks': [],
684 684 'tags': tags,
685 685 }
686 686
687 687 return _call_hook('pre_push', extras, SvnMessageWriter())
688 688
689 689
690 690 def svn_post_commit(repo_path, commit_data, env):
691 691 """
692 692 commit_data is path, rev, txn_id
693 693 """
694 694 if len(commit_data) == 3:
695 695 path, commit_id, txn_id = commit_data
696 696 elif len(commit_data) == 2:
697 697 log.error('Failed to extract txn_id from commit_data using legacy method. '
698 698 'Some functionality might be limited')
699 699 path, commit_id = commit_data
700 700 txn_id = None
701 701
702 702 branches = []
703 703 tags = []
704 704
705 705 if env.get('RC_SCM_DATA'):
706 706 extras = json.loads(env['RC_SCM_DATA'])
707 707 else:
708 708 # fallback method to read from TXN-ID stored data
709 709 extras = _get_extras_from_commit_id(commit_id, path)
710 710 if not extras:
711 711 return 0
712 712
713 713 extras['hook_type'] = 'post_commit'
714 714 extras['commit_ids'] = [commit_id]
715 715 extras['txn_id'] = txn_id
716 716 extras['new_refs'] = {
717 717 'branches': branches,
718 718 'bookmarks': [],
719 719 'tags': tags,
720 720 'total_commits': 1,
721 721 }
722 722
723 723 if 'repo_size' in extras['hooks']:
724 724 try:
725 725 _call_hook('repo_size', extras, SvnMessageWriter())
726 726 except Exception:
727 727 pass
728 728
729 729 return _call_hook('post_push', extras, SvnMessageWriter())
General Comments 0
You need to be logged in to leave comments. Login now