##// END OF EJS Templates
release: Merge default into stable for release preparation
marcink -
r810:8ec05ec7 merge stable
parent child Browse files
Show More
@@ -0,0 +1,8 b''
1 ## special libraries we could extend the requirements.txt file with to add some
2 ## custom libraries useful for debug and memory tracing
3
4 ## uncomment inclusion of this file in requirements.txt run make generate-pkgs and nix-shell
5
6 objgraph
7 memory-profiler
8 pympler
@@ -0,0 +1,27 b''
1 # -*- coding: utf-8 -*-
2
3 # RhodeCode VCSServer provides access to different vcs backends via network.
4 # Copyright (C) 2014-2019 RhodeCode GmbH
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
20
21 counter = 0
22
23
24 def get_request_counter(request):
25 global counter
26 counter += 1
27 return counter
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -0,0 +1,32 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2019 RhodeCode GmbH
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
18
19 class RemoteBase(object):
20 EMPTY_COMMIT = '0' * 40
21
22 @property
23 def region(self):
24 return self._factory._cache_region
25
26 def _cache_on(self, wire):
27 context = wire.get('context', '')
28 context_uid = '{}'.format(context)
29 repo_id = wire.get('repo_id', '')
30 cache = wire.get('cache', True)
31 cache_on = context and cache
32 return cache_on, context_uid, repo_id
@@ -1,5 +1,5 b''
1 [bumpversion]
1 [bumpversion]
2 current_version = 4.17.4
2 current_version = 4.18.0
3 message = release: Bump version {current_version} to {new_version}
3 message = release: Bump version {current_version} to {new_version}
4
4
5 [bumpversion:file:vcsserver/VERSION]
5 [bumpversion:file:vcsserver/VERSION]
@@ -5,12 +5,10 b' done = false'
5 done = true
5 done = true
6
6
7 [task:fixes_on_stable]
7 [task:fixes_on_stable]
8 done = true
9
8
10 [task:pip2nix_generated]
9 [task:pip2nix_generated]
11 done = true
12
10
13 [release]
11 [release]
14 state = prepared
12 state = in_progress
15 version = 4.17.4
13 version = 4.18.0
16
14
@@ -1,50 +1,200 b''
1 ################################################################################
1 ## -*- coding: utf-8 -*-
2 # RhodeCode VCSServer with HTTP Backend - configuration #
3 ################################################################################
4
2
3 ; #################################
4 ; RHODECODE VCSSERVER CONFIGURATION
5 ; #################################
5
6
6 [server:main]
7 [server:main]
7 ## COMMON ##
8 ; COMMON HOST/IP CONFIG
8 host = 0.0.0.0
9 host = 0.0.0.0
9 port = 9900
10 port = 9900
10
11
12 ; ##################################################
13 ; WAITRESS WSGI SERVER - Recommended for Development
14 ; ##################################################
15
16 ; use server type
11 use = egg:waitress#main
17 use = egg:waitress#main
12
18
19 ; number of worker threads
20 threads = 5
21
22 ; MAX BODY SIZE 100GB
23 max_request_body_size = 107374182400
24
25 ; Use poll instead of select, fixes file descriptors limits problems.
26 ; May not work on old windows systems.
27 asyncore_use_poll = true
28
29
30 ; ###########################
31 ; GUNICORN APPLICATION SERVER
32 ; ###########################
33
34 ; run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
35
36 ; Module to use, this setting shouldn't be changed
37 #use = egg:gunicorn#main
38
39 ; Sets the number of process workers. More workers means more concurrent connections
40 ; RhodeCode can handle at the same time. Each additional worker also it increases
41 ; memory usage as each has it's own set of caches.
42 ; Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
43 ; than 8-10 unless for really big deployments .e.g 700-1000 users.
44 ; `instance_id = *` must be set in the [app:main] section below (which is the default)
45 ; when using more than 1 worker.
46 #workers = 2
47
48 ; Gunicorn access log level
49 #loglevel = info
50
51 ; Process name visible in process list
52 #proc_name = rhodecode_vcsserver
53
54 ; Type of worker class, one of `sync`, `gevent`
55 ; currently `sync` is the only option allowed.
56 #worker_class = sync
57
58 ; The maximum number of simultaneous clients. Valid only for gevent
59 #worker_connections = 10
60
61 ; Max number of requests that worker will handle before being gracefully restarted.
62 ; Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
63 #max_requests = 1000
64 #max_requests_jitter = 30
65
66 ; Amount of time a worker can spend with handling a request before it
67 ; gets killed and restarted. By default set to 21600 (6hrs)
68 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
69 #timeout = 21600
70
71 ; The maximum size of HTTP request line in bytes.
72 ; 0 for unlimited
73 #limit_request_line = 0
74
75 ; Limit the number of HTTP headers fields in a request.
76 ; By default this value is 100 and can't be larger than 32768.
77 #limit_request_fields = 32768
78
79 ; Limit the allowed size of an HTTP request header field.
80 ; Value is a positive number or 0.
81 ; Setting it to 0 will allow unlimited header field sizes.
82 #limit_request_field_size = 0
83
84 ; Timeout for graceful workers restart.
85 ; After receiving a restart signal, workers have this much time to finish
86 ; serving requests. Workers still alive after the timeout (starting from the
87 ; receipt of the restart signal) are force killed.
88 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
89 #graceful_timeout = 3600
90
91 # The number of seconds to wait for requests on a Keep-Alive connection.
92 # Generally set in the 1-5 seconds range.
93 #keepalive = 2
94
95 ; Maximum memory usage that each worker can use before it will receive a
96 ; graceful restart signal 0 = memory monitoring is disabled
97 ; Examples: 268435456 (256MB), 536870912 (512MB)
98 ; 1073741824 (1GB), 2147483648 (2GB), 4294967296 (4GB)
99 #memory_max_usage = 0
100
101 ; How often in seconds to check for memory usage for each gunicorn worker
102 #memory_usage_check_interval = 60
103
104 ; Threshold value for which we don't recycle worker if GarbageCollection
105 ; frees up enough resources. Before each restart we try to run GC on worker
106 ; in case we get enough free memory after that, restart will not happen.
107 #memory_usage_recovery_threshold = 0.8
108
13
109
14 [app:main]
110 [app:main]
111 ; The %(here)s variable will be replaced with the absolute path of parent directory
112 ; of this file
15 use = egg:rhodecode-vcsserver
113 use = egg:rhodecode-vcsserver
16
114
17 pyramid.default_locale_name = en
115
116 ; #############
117 ; DEBUG OPTIONS
118 ; #############
119
120 # During development the we want to have the debug toolbar enabled
18 pyramid.includes =
121 pyramid.includes =
122 pyramid_debugtoolbar
19
123
20 ## default locale used by VCS systems
124 debugtoolbar.hosts = 0.0.0.0/0
125 debugtoolbar.exclude_prefixes =
126 /css
127 /fonts
128 /images
129 /js
130
131 ; #################
132 ; END DEBUG OPTIONS
133 ; #################
134
135 ; Pyramid default locales, we need this to be set
136 pyramid.default_locale_name = en
137
138 ; default locale used by VCS systems
21 locale = en_US.UTF-8
139 locale = en_US.UTF-8
22
140
23
141 ; path to binaries for vcsserver, it should be set by the installer
24 ## path to binaries for vcsserver, it should be set by the installer
142 ; at installation time, e.g /home/user/vcsserver-1/profile/bin
25 ## at installation time, e.g /home/user/vcsserver-1/profile/bin
143 ; it can also be a path to nix-build output in case of development
26 core.binary_dir = ""
144 core.binary_dir = ""
27
145
28 ## Custom exception store path, defaults to TMPDIR
146 ; Custom exception store path, defaults to TMPDIR
29 ## This is used to store exception from RhodeCode in shared directory
147 ; This is used to store exception from RhodeCode in shared directory
30 #exception_tracker.store_path =
148 #exception_tracker.store_path =
31
149
32 ## Default cache dir for caches. Putting this into a ramdisk
150 ; #############
33 ## can boost performance, eg. /tmpfs/data_ramdisk, however this directory might require
151 ; DOGPILE CACHE
34 ## large amount of space
152 ; #############
35 cache_dir = %(here)s/rcdev/data
153
154 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
155 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
156 cache_dir = %(here)s/data
157
158 ; ***************************************
159 ; `repo_object` cache, default file based
160 ; ***************************************
161
162 ; `repo_object` cache settings for vcs methods for repositories
163 rc_cache.repo_object.backend = dogpile.cache.rc.file_namespace
164
165 ; cache auto-expires after N seconds
166 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
167 rc_cache.repo_object.expiration_time = 2592000
168
169 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
170 #rc_cache.repo_object.arguments.filename = /tmp/vcsserver_cache.db
36
171
37 ## cache region for storing repo_objects cache
172 ; ***********************************************************
38 rc_cache.repo_object.backend = dogpile.cache.rc.memory_lru
173 ; `repo_object` cache with redis backend
39 ## cache auto-expires after N seconds
174 ; recommended for larger instance, and for better performance
40 rc_cache.repo_object.expiration_time = 300
175 ; ***********************************************************
41 ## max size of LRU, old values will be discarded if the size of cache reaches max_size
176
42 rc_cache.repo_object.max_size = 100
177 ; `repo_object` cache settings for vcs methods for repositories
178 #rc_cache.repo_object.backend = dogpile.cache.rc.redis_msgpack
179
180 ; cache auto-expires after N seconds
181 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
182 #rc_cache.repo_object.expiration_time = 2592000
183
184 ; redis_expiration_time needs to be greater then expiration_time
185 #rc_cache.repo_object.arguments.redis_expiration_time = 3592000
186
187 #rc_cache.repo_object.arguments.host = localhost
188 #rc_cache.repo_object.arguments.port = 6379
189 #rc_cache.repo_object.arguments.db = 5
190 #rc_cache.repo_object.arguments.socket_timeout = 30
191 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
192 #rc_cache.repo_object.arguments.distributed_lock = true
43
193
44
194
45 ################################
195 ; #####################
46 ### LOGGING CONFIGURATION ####
196 ; LOGGING CONFIGURATION
47 ################################
197 ; #####################
48 [loggers]
198 [loggers]
49 keys = root, vcsserver
199 keys = root, vcsserver
50
200
@@ -54,9 +204,9 b' keys = console'
54 [formatters]
204 [formatters]
55 keys = generic
205 keys = generic
56
206
57 #############
207 ; #######
58 ## LOGGERS ##
208 ; LOGGERS
59 #############
209 ; #######
60 [logger_root]
210 [logger_root]
61 level = NOTSET
211 level = NOTSET
62 handlers = console
212 handlers = console
@@ -68,9 +218,9 b' qualname = vcsserver'
68 propagate = 1
218 propagate = 1
69
219
70
220
71 ##############
221 ; ########
72 ## HANDLERS ##
222 ; HANDLERS
73 ##############
223 ; ########
74
224
75 [handler_console]
225 [handler_console]
76 class = StreamHandler
226 class = StreamHandler
@@ -78,9 +228,9 b' args = (sys.stderr,)'
78 level = DEBUG
228 level = DEBUG
79 formatter = generic
229 formatter = generic
80
230
81 ################
231 ; ##########
82 ## FORMATTERS ##
232 ; FORMATTERS
83 ################
233 ; ##########
84
234
85 [formatter_generic]
235 [formatter_generic]
86 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
236 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
@@ -1,58 +1,26 b''
1 """
1 """
2 gunicorn config extension and hooks. Sets additional configuration that is
2 Gunicorn config extension and hooks. This config file adds some extra settings and memory management.
3 available post the .ini config.
3 Gunicorn configuration should be managed by .ini files entries of RhodeCode or VCSServer
4
5 - workers = ${cpu_number}
6 - threads = 1
7 - proc_name = ${gunicorn_proc_name}
8 - worker_class = sync
9 - worker_connections = 10
10 - max_requests = 1000
11 - max_requests_jitter = 30
12 - timeout = 21600
13
14 """
4 """
15
5
16 import multiprocessing
6 import gc
7 import os
17 import sys
8 import sys
9 import math
18 import time
10 import time
19 import datetime
20 import threading
11 import threading
21 import traceback
12 import traceback
13 import random
22 from gunicorn.glogging import Logger
14 from gunicorn.glogging import Logger
23
15
24
16
17 def get_workers():
18 import multiprocessing
19 return multiprocessing.cpu_count() * 2 + 1
20
25 # GLOBAL
21 # GLOBAL
26 errorlog = '-'
22 errorlog = '-'
27 accesslog = '-'
23 accesslog = '-'
28 loglevel = 'debug'
29
30 # SECURITY
31
32 # The maximum size of HTTP request line in bytes.
33 # 0 for unlimited
34 limit_request_line = 0
35
36 # Limit the number of HTTP headers fields in a request.
37 # By default this value is 100 and can't be larger than 32768.
38 limit_request_fields = 10240
39
40 # Limit the allowed size of an HTTP request header field.
41 # Value is a positive number or 0.
42 # Setting it to 0 will allow unlimited header field sizes.
43 limit_request_field_size = 0
44
45
46 # Timeout for graceful workers restart.
47 # After receiving a restart signal, workers have this much time to finish
48 # serving requests. Workers still alive after the timeout (starting from the
49 # receipt of the restart signal) are force killed.
50 graceful_timeout = 30
51
52
53 # The number of seconds to wait for requests on a Keep-Alive connection.
54 # Generally set in the 1-5 seconds range.
55 keepalive = 2
56
24
57
25
58 # SERVER MECHANICS
26 # SERVER MECHANICS
@@ -63,38 +31,178 b' tmp_upload_dir = None'
63
31
64 # Custom log format
32 # Custom log format
65 access_log_format = (
33 access_log_format = (
66 '%(t)s [%(p)-8s] GNCRN %(h)-15s rqt:%(L)s %(s)s %(b)-6s "%(m)s:%(U)s %(q)s" usr:%(u)s "%(f)s" "%(a)s"')
34 '%(t)s %(p)s INFO [GNCRN] %(h)-15s rqt:%(L)s %(s)s %(b)-6s "%(m)s:%(U)s %(q)s" usr:%(u)s "%(f)s" "%(a)s"')
67
35
68 # self adjust workers based on CPU count
36 # self adjust workers based on CPU count
69 # workers = multiprocessing.cpu_count() * 2 + 1
37 # workers = get_workers()
38
39
40 def _get_process_rss(pid=None):
41 try:
42 import psutil
43 if pid:
44 proc = psutil.Process(pid)
45 else:
46 proc = psutil.Process()
47 return proc.memory_info().rss
48 except Exception:
49 return None
70
50
71
51
72 def post_fork(server, worker):
52 def _get_config(ini_path):
73 server.log.info("[<%-10s>] WORKER spawned", worker.pid)
53
54 try:
55 import configparser
56 except ImportError:
57 import ConfigParser as configparser
58 try:
59 config = configparser.RawConfigParser()
60 config.read(ini_path)
61 return config
62 except Exception:
63 return None
64
65
66 def _time_with_offset(memory_usage_check_interval):
67 return time.time() - random.randint(0, memory_usage_check_interval/2.0)
74
68
75
69
76 def pre_fork(server, worker):
70 def pre_fork(server, worker):
77 pass
71 pass
78
72
79
73
74 def post_fork(server, worker):
75
76 # memory spec defaults
77 _memory_max_usage = 0
78 _memory_usage_check_interval = 60
79 _memory_usage_recovery_threshold = 0.8
80
81 ini_path = os.path.abspath(server.cfg.paste)
82 conf = _get_config(ini_path)
83
84 section = 'server:main'
85 if conf and conf.has_section(section):
86
87 if conf.has_option(section, 'memory_max_usage'):
88 _memory_max_usage = conf.getint(section, 'memory_max_usage')
89
90 if conf.has_option(section, 'memory_usage_check_interval'):
91 _memory_usage_check_interval = conf.getint(section, 'memory_usage_check_interval')
92
93 if conf.has_option(section, 'memory_usage_recovery_threshold'):
94 _memory_usage_recovery_threshold = conf.getfloat(section, 'memory_usage_recovery_threshold')
95
96 worker._memory_max_usage = _memory_max_usage
97 worker._memory_usage_check_interval = _memory_usage_check_interval
98 worker._memory_usage_recovery_threshold = _memory_usage_recovery_threshold
99
100 # register memory last check time, with some random offset so we don't recycle all
101 # at once
102 worker._last_memory_check_time = _time_with_offset(_memory_usage_check_interval)
103
104 if _memory_max_usage:
105 server.log.info("[%-10s] WORKER spawned with max memory set at %s", worker.pid,
106 _format_data_size(_memory_max_usage))
107 else:
108 server.log.info("[%-10s] WORKER spawned", worker.pid)
109
110
80 def pre_exec(server):
111 def pre_exec(server):
81 server.log.info("Forked child, re-executing.")
112 server.log.info("Forked child, re-executing.")
82
113
83
114
84 def on_starting(server):
115 def on_starting(server):
85 server.log.info("Server is starting.")
116 server_lbl = '{} {}'.format(server.proc_name, server.address)
117 server.log.info("Server %s is starting.", server_lbl)
86
118
87
119
88 def when_ready(server):
120 def when_ready(server):
89 server.log.info("Server is ready. Spawning workers")
121 server.log.info("Server %s is ready. Spawning workers", server)
90
122
91
123
92 def on_reload(server):
124 def on_reload(server):
93 pass
125 pass
94
126
95
127
128 def _format_data_size(size, unit="B", precision=1, binary=True):
129 """Format a number using SI units (kilo, mega, etc.).
130
131 ``size``: The number as a float or int.
132
133 ``unit``: The unit name in plural form. Examples: "bytes", "B".
134
135 ``precision``: How many digits to the right of the decimal point. Default
136 is 1. 0 suppresses the decimal point.
137
138 ``binary``: If false, use base-10 decimal prefixes (kilo = K = 1000).
139 If true, use base-2 binary prefixes (kibi = Ki = 1024).
140
141 ``full_name``: If false (default), use the prefix abbreviation ("k" or
142 "Ki"). If true, use the full prefix ("kilo" or "kibi"). If false,
143 use abbreviation ("k" or "Ki").
144
145 """
146
147 if not binary:
148 base = 1000
149 multiples = ('', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
150 else:
151 base = 1024
152 multiples = ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')
153
154 sign = ""
155 if size > 0:
156 m = int(math.log(size, base))
157 elif size < 0:
158 sign = "-"
159 size = -size
160 m = int(math.log(size, base))
161 else:
162 m = 0
163 if m > 8:
164 m = 8
165
166 if m == 0:
167 precision = '%.0f'
168 else:
169 precision = '%%.%df' % precision
170
171 size = precision % (size / math.pow(base, m))
172
173 return '%s%s %s%s' % (sign, size.strip(), multiples[m], unit)
174
175
176 def _check_memory_usage(worker):
177 memory_max_usage = worker._memory_max_usage
178 if not memory_max_usage:
179 return
180
181 memory_usage_check_interval = worker._memory_usage_check_interval
182 memory_usage_recovery_threshold = memory_max_usage * worker._memory_usage_recovery_threshold
183
184 elapsed = time.time() - worker._last_memory_check_time
185 if elapsed > memory_usage_check_interval:
186 mem_usage = _get_process_rss()
187 if mem_usage and mem_usage > memory_max_usage:
188 worker.log.info(
189 "memory usage %s > %s, forcing gc",
190 _format_data_size(mem_usage), _format_data_size(memory_max_usage))
191 # Try to clean it up by forcing a full collection.
192 gc.collect()
193 mem_usage = _get_process_rss()
194 if mem_usage > memory_usage_recovery_threshold:
195 # Didn't clean up enough, we'll have to terminate.
196 worker.log.warning(
197 "memory usage %s > %s after gc, quitting",
198 _format_data_size(mem_usage), _format_data_size(memory_max_usage))
199 # This will cause worker to auto-restart itself
200 worker.alive = False
201 worker._last_memory_check_time = time.time()
202
203
96 def worker_int(worker):
204 def worker_int(worker):
97 worker.log.info("[<%-10s>] worker received INT or QUIT signal", worker.pid)
205 worker.log.info("[%-10s] worker received INT or QUIT signal", worker.pid)
98
206
99 # get traceback info, on worker crash
207 # get traceback info, on worker crash
100 id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
208 id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
@@ -110,15 +218,15 b' def worker_int(worker):'
110
218
111
219
112 def worker_abort(worker):
220 def worker_abort(worker):
113 worker.log.info("[<%-10s>] worker received SIGABRT signal", worker.pid)
221 worker.log.info("[%-10s] worker received SIGABRT signal", worker.pid)
114
222
115
223
116 def worker_exit(server, worker):
224 def worker_exit(server, worker):
117 worker.log.info("[<%-10s>] worker exit", worker.pid)
225 worker.log.info("[%-10s] worker exit", worker.pid)
118
226
119
227
120 def child_exit(server, worker):
228 def child_exit(server, worker):
121 worker.log.info("[<%-10s>] worker child exit", worker.pid)
229 worker.log.info("[%-10s] worker child exit", worker.pid)
122
230
123
231
124 def pre_request(worker, req):
232 def pre_request(worker, req):
@@ -129,9 +237,12 b' def pre_request(worker, req):'
129
237
130 def post_request(worker, req, environ, resp):
238 def post_request(worker, req, environ, resp):
131 total_time = time.time() - worker.start_time
239 total_time = time.time() - worker.start_time
240 # Gunicorn sometimes has problems with reading the status_code
241 status_code = getattr(resp, 'status_code', '')
132 worker.log.debug(
242 worker.log.debug(
133 "GNCRN POST WORKER [cnt:%s]: %s %s resp: %s, Load Time: %.3fs",
243 "GNCRN POST WORKER [cnt:%s]: %s %s resp: %s, Load Time: %.4fs",
134 worker.nr, req.method, req.path, resp.status_code, total_time)
244 worker.nr, req.method, req.path, status_code, total_time)
245 _check_memory_usage(worker)
135
246
136
247
137 class RhodeCodeLogger(Logger):
248 class RhodeCodeLogger(Logger):
@@ -1,71 +1,163 b''
1 ################################################################################
1 ## -*- coding: utf-8 -*-
2 # RhodeCode VCSServer with HTTP Backend - configuration #
3 ################################################################################
4
2
3 ; #################################
4 ; RHODECODE VCSSERVER CONFIGURATION
5 ; #################################
5
6
6 [server:main]
7 [server:main]
7 ## COMMON ##
8 ; COMMON HOST/IP CONFIG
8 host = 127.0.0.1
9 host = 127.0.0.1
9 port = 9900
10 port = 9900
10
11
11
12
12 ##########################
13 ; ###########################
13 ## GUNICORN WSGI SERVER ##
14 ; GUNICORN APPLICATION SERVER
14 ##########################
15 ; ###########################
15 ## run with gunicorn --log-config vcsserver.ini --paste vcsserver.ini
16
17 ; run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
18
19 ; Module to use, this setting shouldn't be changed
16 use = egg:gunicorn#main
20 use = egg:gunicorn#main
17 ## Sets the number of process workers. Recommended
21
18 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
22 ; Sets the number of process workers. More workers means more concurrent connections
23 ; RhodeCode can handle at the same time. Each additional worker also it increases
24 ; memory usage as each has it's own set of caches.
25 ; Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
26 ; than 8-10 unless for really big deployments .e.g 700-1000 users.
27 ; `instance_id = *` must be set in the [app:main] section below (which is the default)
28 ; when using more than 1 worker.
19 workers = 2
29 workers = 2
20 ## process name
30
31 ; Gunicorn access log level
32 loglevel = info
33
34 ; Process name visible in process list
21 proc_name = rhodecode_vcsserver
35 proc_name = rhodecode_vcsserver
22 ## type of worker class, currently `sync` is the only option allowed.
36
37 ; Type of worker class, one of `sync`, `gevent`
38 ; currently `sync` is the only option allowed.
23 worker_class = sync
39 worker_class = sync
24 ## The maximum number of simultaneous clients. Valid only for Gevent
40
25 #worker_connections = 10
41 ; The maximum number of simultaneous clients. Valid only for gevent
26 ## max number of requests that worker will handle before being gracefully
42 worker_connections = 10
27 ## restarted, could prevent memory leaks
43
44 ; Max number of requests that worker will handle before being gracefully restarted.
45 ; Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
28 max_requests = 1000
46 max_requests = 1000
29 max_requests_jitter = 30
47 max_requests_jitter = 30
30 ## amount of time a worker can spend with handling a request before it
48
31 ## gets killed and restarted. Set to 6hrs
49 ; Amount of time a worker can spend with handling a request before it
50 ; gets killed and restarted. By default set to 21600 (6hrs)
51 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
32 timeout = 21600
52 timeout = 21600
33
53
54 ; The maximum size of HTTP request line in bytes.
55 ; 0 for unlimited
56 limit_request_line = 0
57
58 ; Limit the number of HTTP headers fields in a request.
59 ; By default this value is 100 and can't be larger than 32768.
60 limit_request_fields = 32768
61
62 ; Limit the allowed size of an HTTP request header field.
63 ; Value is a positive number or 0.
64 ; Setting it to 0 will allow unlimited header field sizes.
65 limit_request_field_size = 0
66
67 ; Timeout for graceful workers restart.
68 ; After receiving a restart signal, workers have this much time to finish
69 ; serving requests. Workers still alive after the timeout (starting from the
70 ; receipt of the restart signal) are force killed.
71 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
72 graceful_timeout = 3600
73
74 # The number of seconds to wait for requests on a Keep-Alive connection.
75 # Generally set in the 1-5 seconds range.
76 keepalive = 2
77
78 ; Maximum memory usage that each worker can use before it will receive a
79 ; graceful restart signal 0 = memory monitoring is disabled
80 ; Examples: 268435456 (256MB), 536870912 (512MB)
81 ; 1073741824 (1GB), 2147483648 (2GB), 4294967296 (4GB)
82 memory_max_usage = 0
83
84 ; How often in seconds to check for memory usage for each gunicorn worker
85 memory_usage_check_interval = 60
86
87 ; Threshold value for which we don't recycle worker if GarbageCollection
88 ; frees up enough resources. Before each restart we try to run GC on worker
89 ; in case we get enough free memory after that, restart will not happen.
90 memory_usage_recovery_threshold = 0.8
91
34
92
35 [app:main]
93 [app:main]
94 ; The %(here)s variable will be replaced with the absolute path of parent directory
95 ; of this file
36 use = egg:rhodecode-vcsserver
96 use = egg:rhodecode-vcsserver
37
97
98 ; Pyramid default locales, we need this to be set
38 pyramid.default_locale_name = en
99 pyramid.default_locale_name = en
39 pyramid.includes =
40
100
41 ## default locale used by VCS systems
101 ; default locale used by VCS systems
42 locale = en_US.UTF-8
102 locale = en_US.UTF-8
43
103
44
104 ; path to binaries for vcsserver, it should be set by the installer
45 ## path to binaries for vcsserver, it should be set by the installer
105 ; at installation time, e.g /home/user/vcsserver-1/profile/bin
46 ## at installation time, e.g /home/user/vcsserver-1/profile/bin
106 ; it can also be a path to nix-build output in case of development
47 core.binary_dir = ""
107 core.binary_dir = ""
48
108
49 ## Custom exception store path, defaults to TMPDIR
109 ; Custom exception store path, defaults to TMPDIR
50 ## This is used to store exception from RhodeCode in shared directory
110 ; This is used to store exception from RhodeCode in shared directory
51 #exception_tracker.store_path =
111 #exception_tracker.store_path =
52
112
53 ## Default cache dir for caches. Putting this into a ramdisk
113 ; #############
54 ## can boost performance, eg. /tmpfs/data_ramdisk, however this directory might require
114 ; DOGPILE CACHE
55 ## large amount of space
115 ; #############
56 cache_dir = %(here)s/rcdev/data
116
117 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
118 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
119 cache_dir = %(here)s/data
120
121 ; ***************************************
122 ; `repo_object` cache, default file based
123 ; ***************************************
124
125 ; `repo_object` cache settings for vcs methods for repositories
126 rc_cache.repo_object.backend = dogpile.cache.rc.file_namespace
127
128 ; cache auto-expires after N seconds
129 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
130 rc_cache.repo_object.expiration_time = 2592000
131
132 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
133 #rc_cache.repo_object.arguments.filename = /tmp/vcsserver_cache.db
57
134
58 ## cache region for storing repo_objects cache
135 ; ***********************************************************
59 rc_cache.repo_object.backend = dogpile.cache.rc.memory_lru
136 ; `repo_object` cache with redis backend
60 ## cache auto-expires after N seconds
137 ; recommended for larger instance, and for better performance
61 rc_cache.repo_object.expiration_time = 300
138 ; ***********************************************************
62 ## max size of LRU, old values will be discarded if the size of cache reaches max_size
139
63 rc_cache.repo_object.max_size = 100
140 ; `repo_object` cache settings for vcs methods for repositories
141 #rc_cache.repo_object.backend = dogpile.cache.rc.redis_msgpack
142
143 ; cache auto-expires after N seconds
144 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
145 #rc_cache.repo_object.expiration_time = 2592000
146
147 ; redis_expiration_time needs to be greater then expiration_time
148 #rc_cache.repo_object.arguments.redis_expiration_time = 3592000
149
150 #rc_cache.repo_object.arguments.host = localhost
151 #rc_cache.repo_object.arguments.port = 6379
152 #rc_cache.repo_object.arguments.db = 5
153 #rc_cache.repo_object.arguments.socket_timeout = 30
154 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
155 #rc_cache.repo_object.arguments.distributed_lock = true
64
156
65
157
66 ################################
158 ; #####################
67 ### LOGGING CONFIGURATION ####
159 ; LOGGING CONFIGURATION
68 ################################
160 ; #####################
69 [loggers]
161 [loggers]
70 keys = root, vcsserver
162 keys = root, vcsserver
71
163
@@ -75,9 +167,9 b' keys = console'
75 [formatters]
167 [formatters]
76 keys = generic
168 keys = generic
77
169
78 #############
170 ; #######
79 ## LOGGERS ##
171 ; LOGGERS
80 #############
172 ; #######
81 [logger_root]
173 [logger_root]
82 level = NOTSET
174 level = NOTSET
83 handlers = console
175 handlers = console
@@ -89,19 +181,19 b' qualname = vcsserver'
89 propagate = 1
181 propagate = 1
90
182
91
183
92 ##############
184 ; ########
93 ## HANDLERS ##
185 ; HANDLERS
94 ##############
186 ; ########
95
187
96 [handler_console]
188 [handler_console]
97 class = StreamHandler
189 class = StreamHandler
98 args = (sys.stderr,)
190 args = (sys.stderr, )
99 level = DEBUG
191 level = INFO
100 formatter = generic
192 formatter = generic
101
193
102 ################
194 ; ##########
103 ## FORMATTERS ##
195 ; FORMATTERS
104 ################
196 ; ##########
105
197
106 [formatter_generic]
198 [formatter_generic]
107 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
199 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
@@ -1,10 +1,11 b''
1 self: super: {
1 self: super: {
2
2 # bump GIT version
3 # bump GIT version
3 git = super.lib.overrideDerivation super.git (oldAttrs: {
4 git = super.lib.overrideDerivation super.git (oldAttrs: {
4 name = "git-2.19.2";
5 name = "git-2.24.1";
5 src = self.fetchurl {
6 src = self.fetchurl {
6 url = "https://www.kernel.org/pub/software/scm/git/git-2.19.2.tar.xz";
7 url = "https://www.kernel.org/pub/software/scm/git/git-2.24.1.tar.xz";
7 sha256 = "1scbggzghkzzfqg4ky3qh7h9w87c3zya4ls5disz7dbx56is7sgw";
8 sha256 = "0ql5z31vgl7b785gwrf00m129mg7zi9pa65n12ij3mpxx3f28gvj";
8 };
9 };
9
10
10 # patches come from: https://github.com/NixOS/nixpkgs/tree/master/pkgs/applications/version-management/git-and-tools/git
11 # patches come from: https://github.com/NixOS/nixpkgs/tree/master/pkgs/applications/version-management/git-and-tools/git
@@ -18,6 +19,29 b' self: super: {'
18
19
19 });
20 });
20
21
22 libgit2rc = super.lib.overrideDerivation super.libgit2 (oldAttrs: {
23 name = "libgit2-0.28.2";
24 version = "0.28.2";
25
26 src = self.fetchFromGitHub {
27 owner = "libgit2";
28 repo = "libgit2";
29 rev = "v0.28.2";
30 sha256 = "0cm8fvs05rj0baigs2133q5a0sm3pa234y8h6hmwhl2bz9xq3k4b";
31 };
32
33 cmakeFlags = [ "-DTHREADSAFE=ON" "-DUSE_HTTPS=no"];
34
35 buildInputs = [
36 super.zlib
37 super.libssh2
38 super.openssl
39 super.curl
40 ];
41
42
43 });
44
21 # Override subversion derivation to
45 # Override subversion derivation to
22 # - activate python bindings
46 # - activate python bindings
23 subversion =
47 subversion =
@@ -29,10 +53,10 b' self: super: {'
29 };
53 };
30 in
54 in
31 super.lib.overrideDerivation subversionWithPython (oldAttrs: {
55 super.lib.overrideDerivation subversionWithPython (oldAttrs: {
32 name = "subversion-1.10.2";
56 name = "subversion-1.12.2";
33 src = self.fetchurl {
57 src = self.fetchurl {
34 url = "https://archive.apache.org/dist/subversion/subversion-1.10.2.tar.gz";
58 url = "https://archive.apache.org/dist/subversion/subversion-1.12.2.tar.gz";
35 sha256 = "0xv5z2bg0lw7057g913yc13f60nfj257wvmsq22pr33m4syf26sg";
59 sha256 = "1wr1pklnq67xdzmf237zj6l1hg43yshfkbxvpvd5sv6r0dk7v4pl";
36 };
60 };
37
61
38 ## use internal lz4/utf8proc because it is stable and shipped with SVN
62 ## use internal lz4/utf8proc because it is stable and shipped with SVN
@@ -41,7 +65,7 b' self: super: {'
41 " --with-utf8proc=internal"
65 " --with-utf8proc=internal"
42 ];
66 ];
43
67
44
45 });
68 });
46
69
70
47 }
71 }
@@ -3,9 +3,10 b" and (2) make sure `gitman.info' isn't pr"
3 node names).
3 node names).
4
4
5 diff --git a/Documentation/Makefile b/Documentation/Makefile
5 diff --git a/Documentation/Makefile b/Documentation/Makefile
6 index 26a2342bea..ceccd67ebb 100644
6 --- a/Documentation/Makefile
7 --- a/Documentation/Makefile
7 +++ b/Documentation/Makefile
8 +++ b/Documentation/Makefile
8 @@ -122,7 +122,7 @@
9 @@ -132,7 +132,7 @@ HTML_REPO = ../../git-htmldocs
9
10
10 MAKEINFO = makeinfo
11 MAKEINFO = makeinfo
11 INSTALL_INFO = install-info
12 INSTALL_INFO = install-info
@@ -14,7 +15,7 b' diff --git a/Documentation/Makefile b/Do'
14 DBLATEX = dblatex
15 DBLATEX = dblatex
15 ASCIIDOC_DBLATEX_DIR = /etc/asciidoc/dblatex
16 ASCIIDOC_DBLATEX_DIR = /etc/asciidoc/dblatex
16 DBLATEX_COMMON = -p $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.xsl -s $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.sty
17 DBLATEX_COMMON = -p $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.xsl -s $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.sty
17 @@ -240,7 +240,7 @@
18 @@ -250,7 +250,7 @@ man1: $(DOC_MAN1)
18 man5: $(DOC_MAN5)
19 man5: $(DOC_MAN5)
19 man7: $(DOC_MAN7)
20 man7: $(DOC_MAN7)
20
21
@@ -23,7 +24,7 b' diff --git a/Documentation/Makefile b/Do'
23
24
24 pdf: user-manual.pdf
25 pdf: user-manual.pdf
25
26
26 @@ -256,10 +256,9 @@
27 @@ -266,10 +266,9 @@ install-man: man
27
28
28 install-info: info
29 install-info: info
29 $(INSTALL) -d -m 755 $(DESTDIR)$(infodir)
30 $(INSTALL) -d -m 755 $(DESTDIR)$(infodir)
@@ -1,7 +1,8 b''
1 diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
1 diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
2 index 1afe9fc858..05dd7c3a90 100644
2 --- a/Documentation/git-send-email.txt
3 --- a/Documentation/git-send-email.txt
3 +++ b/Documentation/git-send-email.txt
4 +++ b/Documentation/git-send-email.txt
4 @@ -208,8 +208,7 @@ a password is obtained using 'git-credential'.
5 @@ -215,8 +215,7 @@ a password is obtained using 'git-credential'.
5 specify a full pathname of a sendmail-like program instead;
6 specify a full pathname of a sendmail-like program instead;
6 the program must support the `-i` option. Default value can
7 the program must support the `-i` option. Default value can
7 be specified by the `sendemail.smtpServer` configuration
8 be specified by the `sendemail.smtpServer` configuration
@@ -12,9 +13,10 b' diff --git a/Documentation/git-send-emai'
12
13
13 --smtp-server-port=<port>::
14 --smtp-server-port=<port>::
14 diff --git a/git-send-email.perl b/git-send-email.perl
15 diff --git a/git-send-email.perl b/git-send-email.perl
16 index 8eb63b5a2f..74a61d8213 100755
15 --- a/git-send-email.perl
17 --- a/git-send-email.perl
16 +++ b/git-send-email.perl
18 +++ b/git-send-email.perl
17 @@ -944,8 +944,7 @@ if (defined $reply_to) {
19 @@ -956,8 +956,7 @@ sub expand_one_alias {
18 }
20 }
19
21
20 if (!defined $smtp_server) {
22 if (!defined $smtp_server) {
@@ -1,94 +1,23 b''
1 diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh
2 index e1d917fd27..e90f8e1414 100644
1 --- a/git-sh-i18n.sh
3 --- a/git-sh-i18n.sh
2 +++ b/git-sh-i18n.sh
4 +++ b/git-sh-i18n.sh
3 @@ -15,87 +15,11 @@
5 @@ -26,7 +26,7 @@ then
4 fi
6 elif test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
5 export TEXTDOMAINDIR
7 then
6
8 : no probing necessary
7 -# First decide what scheme to use...
8 -GIT_INTERNAL_GETTEXT_SH_SCHEME=fallthrough
9 -if test -n "$GIT_GETTEXT_POISON"
10 -then
11 - GIT_INTERNAL_GETTEXT_SH_SCHEME=poison
12 -elif test -n "@@USE_GETTEXT_SCHEME@@"
13 -then
14 - GIT_INTERNAL_GETTEXT_SH_SCHEME="@@USE_GETTEXT_SCHEME@@"
15 -elif test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
16 -then
17 - : no probing necessary
18 -elif type gettext.sh >/dev/null 2>&1
9 -elif type gettext.sh >/dev/null 2>&1
19 -then
10 +elif type @gettext@/bin/gettext.sh >/dev/null 2>&1
20 - # GNU libintl's gettext.sh
11 then
21 - GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu
12 # GNU libintl's gettext.sh
22 -elif test "$(gettext -h 2>&1)" = "-h"
13 GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu
23 -then
14 @@ -43,7 +43,8 @@ export GIT_INTERNAL_GETTEXT_SH_SCHEME
24 - # gettext binary exists but no gettext.sh. likely to be a gettext
15 case "$GIT_INTERNAL_GETTEXT_SH_SCHEME" in
25 - # binary on a Solaris or something that is not GNU libintl and
16 gnu)
26 - # lack eval_gettext.
17 # Use libintl's gettext.sh, or fall back to English if we can't.
27 - GIT_INTERNAL_GETTEXT_SH_SCHEME=gettext_without_eval_gettext
28 -fi
29 -export GIT_INTERNAL_GETTEXT_SH_SCHEME
30 -
31 -# ... and then follow that decision.
32 -case "$GIT_INTERNAL_GETTEXT_SH_SCHEME" in
33 -gnu)
34 - # Use libintl's gettext.sh, or fall back to English if we can't.
35 - . gettext.sh
18 - . gettext.sh
36 - ;;
19 + . @gettext@/bin/gettext.sh
37 -gettext_without_eval_gettext)
38 - # Solaris has a gettext(1) but no eval_gettext(1)
39 - eval_gettext () {
40 - gettext "$1" | (
41 - export PATH $(git sh-i18n--envsubst --variables "$1");
42 - git sh-i18n--envsubst "$1"
43 - )
44 - }
45 -
46 - eval_ngettext () {
47 - ngettext "$1" "$2" "$3" | (
48 - export PATH $(git sh-i18n--envsubst --variables "$2");
49 - git sh-i18n--envsubst "$2"
50 - )
51 - }
52 - ;;
53 -poison)
54 - # Emit garbage so that tests that incorrectly rely on translatable
55 - # strings will fail.
56 - gettext () {
57 - printf "%s" "# GETTEXT POISON #"
58 - }
59 -
60 - eval_gettext () {
61 - printf "%s" "# GETTEXT POISON #"
62 - }
63 -
64 - eval_ngettext () {
65 - printf "%s" "# GETTEXT POISON #"
66 - }
67 - ;;
68 -*)
69 - gettext () {
70 - printf "%s" "$1"
71 - }
72 -
73 - eval_gettext () {
74 - printf "%s" "$1" | (
75 - export PATH $(git sh-i18n--envsubst --variables "$1");
76 - git sh-i18n--envsubst "$1"
77 - )
78 - }
79 +# GNU gettext
80 +export GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu
81 +export PATH=@gettext@/bin:$PATH
20 + export PATH=@gettext@/bin:$PATH
82
21 ;;
83 - eval_ngettext () {
22 gettext_without_eval_gettext)
84 - (test "$3" = 1 && printf "%s" "$1" || printf "%s" "$2") | (
23 # Solaris has a gettext(1) but no eval_gettext(1)
85 - export PATH $(git sh-i18n--envsubst --variables "$2");
86 - git sh-i18n--envsubst "$2"
87 - )
88 - }
89 - ;;
90 -esac
91 +. @gettext@/bin/gettext.sh
92
93 # Git-specific wrapper functions
94 gettextln () {
@@ -1,12 +1,13 b''
1 diff --git a/t/test-lib.sh b/t/test-lib.sh
1 diff --git a/t/test-lib.sh b/t/test-lib.sh
2 index 8665b0a9b6..8bb892b1af 100644
2 --- a/t/test-lib.sh
3 --- a/t/test-lib.sh
3 +++ b/t/test-lib.sh
4 +++ b/t/test-lib.sh
4 @@ -923,7 +923,7 @@
5 @@ -1227,7 +1227,7 @@ elif test -n "$GIT_TEST_INSTALLED"
5 then
6 then
6 GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) ||
7 GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) ||
7 error "Cannot run git from $GIT_TEST_INSTALLED."
8 error "Cannot run git from $GIT_TEST_INSTALLED."
8 - PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR:$PATH
9 - PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR/t/helper:$PATH
9 + PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR/t/helper:$GIT_BUILD_DIR:$PATH
10 + PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR/t/helper:$GIT_BUILD_DIR:$PATH
10 GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
11 GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
11 else # normal case, use ../bin-wrappers only unless $with_dashes:
12 else # normal case, use ../bin-wrappers only unless $with_dashes:
12 git_bin_dir="$GIT_BUILD_DIR/bin-wrappers"
13 if test -n "$no_bin_wrappers"
@@ -1,8 +1,8 b''
1 diff --git a/connect.c b/connect.c
1 diff --git a/connect.c b/connect.c
2 index c3a014c5b..fbca3262b 100644
2 index 4813f005ab..b3f12f3268 100644
3 --- a/connect.c
3 --- a/connect.c
4 +++ b/connect.c
4 +++ b/connect.c
5 @@ -1010,7 +1010,7 @@ static void fill_ssh_args(struct child_process *conn, const char *ssh_host,
5 @@ -1183,7 +1183,7 @@ static void fill_ssh_args(struct child_process *conn, const char *ssh_host,
6
6
7 ssh = getenv("GIT_SSH");
7 ssh = getenv("GIT_SSH");
8 if (!ssh)
8 if (!ssh)
@@ -12,7 +12,7 b' index c3a014c5b..fbca3262b 100644'
12 }
12 }
13
13
14 diff --git a/git-gui/lib/remote_add.tcl b/git-gui/lib/remote_add.tcl
14 diff --git a/git-gui/lib/remote_add.tcl b/git-gui/lib/remote_add.tcl
15 index 480a6b30d..781720424 100644
15 index 480a6b30d0..7817204241 100644
16 --- a/git-gui/lib/remote_add.tcl
16 --- a/git-gui/lib/remote_add.tcl
17 +++ b/git-gui/lib/remote_add.tcl
17 +++ b/git-gui/lib/remote_add.tcl
18 @@ -139,7 +139,7 @@ method _add {} {
18 @@ -139,7 +139,7 @@ method _add {} {
@@ -15,6 +15,12 b' in'
15
15
16 self: super: {
16 self: super: {
17
17
18 "cffi" = super."cffi".override (attrs: {
19 buildInputs = [
20 pkgs.libffi
21 ];
22 });
23
18 "gevent" = super."gevent".override (attrs: {
24 "gevent" = super."gevent".override (attrs: {
19 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
25 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
20 # NOTE: (marcink) odd requirements from gevent aren't set properly,
26 # NOTE: (marcink) odd requirements from gevent aren't set properly,
@@ -52,6 +58,12 b' self: super: {'
52 ];
58 ];
53 });
59 });
54
60
61 "pygit2" = super."pygit2".override (attrs: {
62 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
63 pkgs.libffi
64 pkgs.libgit2rc
65 ];
66 });
55
67
56 # Avoid that base packages screw up the build process
68 # Avoid that base packages screw up the build process
57 inherit (basePythonPackages)
69 inherit (basePythonPackages)
@@ -5,22 +5,22 b''
5
5
6 self: super: {
6 self: super: {
7 "atomicwrites" = super.buildPythonPackage {
7 "atomicwrites" = super.buildPythonPackage {
8 name = "atomicwrites-1.2.1";
8 name = "atomicwrites-1.3.0";
9 doCheck = false;
9 doCheck = false;
10 src = fetchurl {
10 src = fetchurl {
11 url = "https://files.pythonhosted.org/packages/ac/ed/a311712ef6b4355035489f665e63e1a73f9eb371929e3c98e5efd451069e/atomicwrites-1.2.1.tar.gz";
11 url = "https://files.pythonhosted.org/packages/ec/0f/cd484ac8820fed363b374af30049adc8fd13065720fd4f4c6be8a2309da7/atomicwrites-1.3.0.tar.gz";
12 sha256 = "1vmkbw9j0qammwxbxycrs39gvdg4lc2d4lk98kwf8ag2manyi6pc";
12 sha256 = "19ngcscdf3jsqmpcxn6zl5b6anmsajb6izp1smcd1n02midl9abm";
13 };
13 };
14 meta = {
14 meta = {
15 license = [ pkgs.lib.licenses.mit ];
15 license = [ pkgs.lib.licenses.mit ];
16 };
16 };
17 };
17 };
18 "attrs" = super.buildPythonPackage {
18 "attrs" = super.buildPythonPackage {
19 name = "attrs-18.2.0";
19 name = "attrs-19.3.0";
20 doCheck = false;
20 doCheck = false;
21 src = fetchurl {
21 src = fetchurl {
22 url = "https://files.pythonhosted.org/packages/0f/9e/26b1d194aab960063b266170e53c39f73ea0d0d3f5ce23313e0ec8ee9bdf/attrs-18.2.0.tar.gz";
22 url = "https://files.pythonhosted.org/packages/98/c3/2c227e66b5e896e15ccdae2e00bbc69aa46e9a8ce8869cc5fa96310bf612/attrs-19.3.0.tar.gz";
23 sha256 = "0s9ydh058wmmf5v391pym877x4ahxg45dw6a0w4c7s5wgpigdjqh";
23 sha256 = "0wky4h28n7xnr6xv69p9z6kv8bzn50d10c3drmd9ds8gawbcxdzp";
24 };
24 };
25 meta = {
25 meta = {
26 license = [ pkgs.lib.licenses.mit ];
26 license = [ pkgs.lib.licenses.mit ];
@@ -48,6 +48,20 b' self: super: {'
48 license = [ pkgs.lib.licenses.mit ];
48 license = [ pkgs.lib.licenses.mit ];
49 };
49 };
50 };
50 };
51 "cffi" = super.buildPythonPackage {
52 name = "cffi-1.12.3";
53 doCheck = false;
54 propagatedBuildInputs = [
55 self."pycparser"
56 ];
57 src = fetchurl {
58 url = "https://files.pythonhosted.org/packages/93/1a/ab8c62b5838722f29f3daffcc8d4bd61844aa9b5f437341cc890ceee483b/cffi-1.12.3.tar.gz";
59 sha256 = "0x075521fxwv0mfp4cqzk7lvmw4n94bjw601qkcv314z5s182704";
60 };
61 meta = {
62 license = [ pkgs.lib.licenses.mit ];
63 };
64 };
51 "configobj" = super.buildPythonPackage {
65 "configobj" = super.buildPythonPackage {
52 name = "configobj-5.0.6";
66 name = "configobj-5.0.6";
53 doCheck = false;
67 doCheck = false;
@@ -62,6 +76,28 b' self: super: {'
62 license = [ pkgs.lib.licenses.bsdOriginal ];
76 license = [ pkgs.lib.licenses.bsdOriginal ];
63 };
77 };
64 };
78 };
79 "configparser" = super.buildPythonPackage {
80 name = "configparser-4.0.2";
81 doCheck = false;
82 src = fetchurl {
83 url = "https://files.pythonhosted.org/packages/16/4f/48975536bd488d3a272549eb795ac4a13a5f7fcdc8995def77fbef3532ee/configparser-4.0.2.tar.gz";
84 sha256 = "1priacxym85yjcf68hh38w55nqswaxp71ryjyfdk222kg9l85ln7";
85 };
86 meta = {
87 license = [ pkgs.lib.licenses.mit ];
88 };
89 };
90 "contextlib2" = super.buildPythonPackage {
91 name = "contextlib2-0.6.0.post1";
92 doCheck = false;
93 src = fetchurl {
94 url = "https://files.pythonhosted.org/packages/02/54/669207eb72e3d8ae8b38aa1f0703ee87a0e9f88f30d3c0a47bebdb6de242/contextlib2-0.6.0.post1.tar.gz";
95 sha256 = "0bhnr2ac7wy5l85ji909gyljyk85n92w8pdvslmrvc8qih4r1x01";
96 };
97 meta = {
98 license = [ pkgs.lib.licenses.psfl ];
99 };
100 };
65 "cov-core" = super.buildPythonPackage {
101 "cov-core" = super.buildPythonPackage {
66 name = "cov-core-1.15.0";
102 name = "cov-core-1.15.0";
67 doCheck = false;
103 doCheck = false;
@@ -77,11 +113,11 b' self: super: {'
77 };
113 };
78 };
114 };
79 "coverage" = super.buildPythonPackage {
115 "coverage" = super.buildPythonPackage {
80 name = "coverage-4.5.3";
116 name = "coverage-4.5.4";
81 doCheck = false;
117 doCheck = false;
82 src = fetchurl {
118 src = fetchurl {
83 url = "https://files.pythonhosted.org/packages/82/70/2280b5b29a0352519bb95ab0ef1ea942d40466ca71c53a2085bdeff7b0eb/coverage-4.5.3.tar.gz";
119 url = "https://files.pythonhosted.org/packages/85/d5/818d0e603685c4a613d56f065a721013e942088047ff1027a632948bdae6/coverage-4.5.4.tar.gz";
84 sha256 = "02f6m073qdispn96rc616hg0rnmw1pgqzw3bgxwiwza4zf9hirlx";
120 sha256 = "0p0j4di6h8k6ica7jwwj09azdcg4ycxq60i9qsskmsg94cd9yzg0";
85 };
121 };
86 meta = {
122 meta = {
87 license = [ pkgs.lib.licenses.asl20 ];
123 license = [ pkgs.lib.licenses.asl20 ];
@@ -99,14 +135,14 b' self: super: {'
99 };
135 };
100 };
136 };
101 "dogpile.cache" = super.buildPythonPackage {
137 "dogpile.cache" = super.buildPythonPackage {
102 name = "dogpile.cache-0.7.1";
138 name = "dogpile.cache-0.9.0";
103 doCheck = false;
139 doCheck = false;
104 propagatedBuildInputs = [
140 propagatedBuildInputs = [
105 self."decorator"
141 self."decorator"
106 ];
142 ];
107 src = fetchurl {
143 src = fetchurl {
108 url = "https://files.pythonhosted.org/packages/84/3e/dbf1cfc5228f1d3dca80ef714db2c5aaec5cd9efaf54d7e3daef6bc48b19/dogpile.cache-0.7.1.tar.gz";
144 url = "https://files.pythonhosted.org/packages/ac/6a/9ac405686a94b7f009a20a50070a5786b0e1aedc707b88d40d0c4b51a82e/dogpile.cache-0.9.0.tar.gz";
109 sha256 = "0caazmrzhnfqb5yrp8myhw61ny637jj69wcngrpbvi31jlcpy6v9";
145 sha256 = "0sr1fn6b4k5bh0cscd9yi8csqxvj4ngzildav58x5p694mc86j5k";
110 };
146 };
111 meta = {
147 meta = {
112 license = [ pkgs.lib.licenses.bsdOriginal ];
148 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -204,11 +240,11 b' self: super: {'
204 };
240 };
205 };
241 };
206 "hg-evolve" = super.buildPythonPackage {
242 "hg-evolve" = super.buildPythonPackage {
207 name = "hg-evolve-8.5.1";
243 name = "hg-evolve-9.1.0";
208 doCheck = false;
244 doCheck = false;
209 src = fetchurl {
245 src = fetchurl {
210 url = "https://files.pythonhosted.org/packages/e3/ce/6594aa403e3464831d4daf20e45fd2e3ef553d968ac13d2c7fa791d4eedd/hg-evolve-8.5.1.tar.gz";
246 url = "https://files.pythonhosted.org/packages/20/36/5a6655975aa0c663be91098d31a0b24841acad44fe896aa2bdee77c6b883/hg-evolve-9.1.0.tar.gz";
211 sha256 = "09avqn7c1biz97vb1zw91q6nfzydpcqv43mgpfrj7ywp0fscfgf3";
247 sha256 = "1mna81cmzxxn7s2nwz3g1xgdjlcc1axkvfmwg7gjqghwn3pdraps";
212 };
248 };
213 meta = {
249 meta = {
214 license = [ { fullName = "GPLv2+"; } ];
250 license = [ { fullName = "GPLv2+"; } ];
@@ -230,16 +266,33 b' self: super: {'
230 };
266 };
231 };
267 };
232 "hupper" = super.buildPythonPackage {
268 "hupper" = super.buildPythonPackage {
233 name = "hupper-1.6.1";
269 name = "hupper-1.9.1";
234 doCheck = false;
270 doCheck = false;
235 src = fetchurl {
271 src = fetchurl {
236 url = "https://files.pythonhosted.org/packages/85/d9/e005d357b11249c5d70ddf5b7adab2e4c0da4e8b0531ff146917a04fe6c0/hupper-1.6.1.tar.gz";
272 url = "https://files.pythonhosted.org/packages/09/3a/4f215659f31eeffe364a984dba486bfa3907bfcc54b7013bdfe825cebb5f/hupper-1.9.1.tar.gz";
237 sha256 = "0d3cvkc8ssgwk54wvhbifj56ry97qi10pfzwfk8vwzzcikbfp3zy";
273 sha256 = "0pyg879fv9mbwlnbzw2a3234qqycqs9l97h5mpkmk0bvxhi2471v";
238 };
274 };
239 meta = {
275 meta = {
240 license = [ pkgs.lib.licenses.mit ];
276 license = [ pkgs.lib.licenses.mit ];
241 };
277 };
242 };
278 };
279 "importlib-metadata" = super.buildPythonPackage {
280 name = "importlib-metadata-0.23";
281 doCheck = false;
282 propagatedBuildInputs = [
283 self."zipp"
284 self."contextlib2"
285 self."configparser"
286 self."pathlib2"
287 ];
288 src = fetchurl {
289 url = "https://files.pythonhosted.org/packages/5d/44/636bcd15697791943e2dedda0dbe098d8530a38d113b202817133e0b06c0/importlib_metadata-0.23.tar.gz";
290 sha256 = "09mdqdfv5rdrwz80jh9m379gxmvk2vhjfz0fg53hid00icvxf65a";
291 };
292 meta = {
293 license = [ pkgs.lib.licenses.asl20 ];
294 };
295 };
243 "ipdb" = super.buildPythonPackage {
296 "ipdb" = super.buildPythonPackage {
244 name = "ipdb-0.12";
297 name = "ipdb-0.12";
245 doCheck = false;
298 doCheck = false;
@@ -291,50 +344,54 b' self: super: {'
291 };
344 };
292 };
345 };
293 "mako" = super.buildPythonPackage {
346 "mako" = super.buildPythonPackage {
294 name = "mako-1.0.7";
347 name = "mako-1.1.0";
295 doCheck = false;
348 doCheck = false;
296 propagatedBuildInputs = [
349 propagatedBuildInputs = [
297 self."markupsafe"
350 self."markupsafe"
298 ];
351 ];
299 src = fetchurl {
352 src = fetchurl {
300 url = "https://files.pythonhosted.org/packages/eb/f3/67579bb486517c0d49547f9697e36582cd19dafb5df9e687ed8e22de57fa/Mako-1.0.7.tar.gz";
353 url = "https://files.pythonhosted.org/packages/b0/3c/8dcd6883d009f7cae0f3157fb53e9afb05a0d3d33b3db1268ec2e6f4a56b/Mako-1.1.0.tar.gz";
301 sha256 = "1bi5gnr8r8dva06qpyx4kgjc6spm2k1y908183nbbaylggjzs0jf";
354 sha256 = "0jqa3qfpykyn4fmkn0kh6043sfls7br8i2bsdbccazcvk9cijsd3";
302 };
355 };
303 meta = {
356 meta = {
304 license = [ pkgs.lib.licenses.mit ];
357 license = [ pkgs.lib.licenses.mit ];
305 };
358 };
306 };
359 };
307 "markupsafe" = super.buildPythonPackage {
360 "markupsafe" = super.buildPythonPackage {
308 name = "markupsafe-1.1.0";
361 name = "markupsafe-1.1.1";
309 doCheck = false;
362 doCheck = false;
310 src = fetchurl {
363 src = fetchurl {
311 url = "https://files.pythonhosted.org/packages/ac/7e/1b4c2e05809a4414ebce0892fe1e32c14ace86ca7d50c70f00979ca9b3a3/MarkupSafe-1.1.0.tar.gz";
364 url = "https://files.pythonhosted.org/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz";
312 sha256 = "1lxirjypbdd3l9jl4vliilhfnhy7c7f2vlldqg1b0i74khn375sf";
365 sha256 = "0sqipg4fk7xbixqd8kq6rlkxj664d157bdwbh93farcphf92x1r9";
313 };
366 };
314 meta = {
367 meta = {
315 license = [ pkgs.lib.licenses.bsdOriginal ];
368 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd3 ];
316 };
369 };
317 };
370 };
318 "mercurial" = super.buildPythonPackage {
371 "mercurial" = super.buildPythonPackage {
319 name = "mercurial-4.9.1";
372 name = "mercurial-5.1.1";
320 doCheck = false;
373 doCheck = false;
321 src = fetchurl {
374 src = fetchurl {
322 url = "https://files.pythonhosted.org/packages/60/58/a1c52d5f5c0b755e231faf7c4f507dc51fe26d979d36346bc9d28f4f8a75/mercurial-4.9.1.tar.gz";
375 url = "https://files.pythonhosted.org/packages/22/39/e1a95f6048aa0785b82f5faad8281ae7320894a635cb4a57e19479639c92/mercurial-5.1.1.tar.gz";
323 sha256 = "0iybbkd9add066729zg01kwz5hhc1s6lhp9rrnsmzq6ihyxj3p8v";
376 sha256 = "17z42rfjdkrks4grzgac66nfh285zf1pwxd2zwx1p71pw2jqpz1m";
324 };
377 };
325 meta = {
378 meta = {
326 license = [ pkgs.lib.licenses.gpl1 pkgs.lib.licenses.gpl2Plus ];
379 license = [ pkgs.lib.licenses.gpl1 pkgs.lib.licenses.gpl2Plus ];
327 };
380 };
328 };
381 };
329 "mock" = super.buildPythonPackage {
382 "mock" = super.buildPythonPackage {
330 name = "mock-1.0.1";
383 name = "mock-3.0.5";
331 doCheck = false;
384 doCheck = false;
385 propagatedBuildInputs = [
386 self."six"
387 self."funcsigs"
388 ];
332 src = fetchurl {
389 src = fetchurl {
333 url = "https://files.pythonhosted.org/packages/a2/52/7edcd94f0afb721a2d559a5b9aae8af4f8f2c79bc63fdbe8a8a6c9b23bbe/mock-1.0.1.tar.gz";
390 url = "https://files.pythonhosted.org/packages/2e/ab/4fe657d78b270aa6a32f027849513b829b41b0f28d9d8d7f8c3d29ea559a/mock-3.0.5.tar.gz";
334 sha256 = "0kzlsbki6q0awf89rc287f3aj8x431lrajf160a70z0ikhnxsfdq";
391 sha256 = "1hrp6j0yrx2xzylfv02qa8kph661m6yq4p0mc8fnimch9j4psrc3";
335 };
392 };
336 meta = {
393 meta = {
337 license = [ pkgs.lib.licenses.bsdOriginal ];
394 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "OSI Approved :: BSD License"; } ];
338 };
395 };
339 };
396 };
340 "more-itertools" = super.buildPythonPackage {
397 "more-itertools" = super.buildPythonPackage {
@@ -362,6 +419,21 b' self: super: {'
362 license = [ pkgs.lib.licenses.asl20 ];
419 license = [ pkgs.lib.licenses.asl20 ];
363 };
420 };
364 };
421 };
422 "packaging" = super.buildPythonPackage {
423 name = "packaging-19.2";
424 doCheck = false;
425 propagatedBuildInputs = [
426 self."pyparsing"
427 self."six"
428 ];
429 src = fetchurl {
430 url = "https://files.pythonhosted.org/packages/5a/2f/449ded84226d0e2fda8da9252e5ee7731bdf14cd338f622dfcd9934e0377/packaging-19.2.tar.gz";
431 sha256 = "0izwlz9h0bw171a1chr311g2y7n657zjaf4mq4rgm8pp9lbj9f98";
432 };
433 meta = {
434 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
435 };
436 };
365 "pastedeploy" = super.buildPythonPackage {
437 "pastedeploy" = super.buildPythonPackage {
366 name = "pastedeploy-2.0.1";
438 name = "pastedeploy-2.0.1";
367 doCheck = false;
439 doCheck = false;
@@ -374,15 +446,15 b' self: super: {'
374 };
446 };
375 };
447 };
376 "pathlib2" = super.buildPythonPackage {
448 "pathlib2" = super.buildPythonPackage {
377 name = "pathlib2-2.3.4";
449 name = "pathlib2-2.3.5";
378 doCheck = false;
450 doCheck = false;
379 propagatedBuildInputs = [
451 propagatedBuildInputs = [
380 self."six"
452 self."six"
381 self."scandir"
453 self."scandir"
382 ];
454 ];
383 src = fetchurl {
455 src = fetchurl {
384 url = "https://files.pythonhosted.org/packages/b5/f4/9c7cc726ece2498b6c8b62d3262aa43f59039b953fe23c9964ac5e18d40b/pathlib2-2.3.4.tar.gz";
456 url = "https://files.pythonhosted.org/packages/94/d8/65c86584e7e97ef824a1845c72bbe95d79f5b306364fa778a3c3e401b309/pathlib2-2.3.5.tar.gz";
385 sha256 = "1y0f9rkm1924zrc5dn4bwxlhgdkbml82lkcc28l5rgmr7d918q24";
457 sha256 = "0s4qa8c082fdkb17izh4mfgwrjd1n5pya18wvrbwqdvvb5xs9nbc";
386 };
458 };
387 meta = {
459 meta = {
388 license = [ pkgs.lib.licenses.mit ];
460 license = [ pkgs.lib.licenses.mit ];
@@ -446,37 +518,40 b' self: super: {'
446 };
518 };
447 };
519 };
448 "pluggy" = super.buildPythonPackage {
520 "pluggy" = super.buildPythonPackage {
449 name = "pluggy-0.11.0";
521 name = "pluggy-0.13.1";
450 doCheck = false;
522 doCheck = false;
523 propagatedBuildInputs = [
524 self."importlib-metadata"
525 ];
451 src = fetchurl {
526 src = fetchurl {
452 url = "https://files.pythonhosted.org/packages/0d/a1/862ab336e8128fde20981d2c1aa8506693412daf5083b1911d539412676b/pluggy-0.11.0.tar.gz";
527 url = "https://files.pythonhosted.org/packages/f8/04/7a8542bed4b16a65c2714bf76cf5a0b026157da7f75e87cc88774aa10b14/pluggy-0.13.1.tar.gz";
453 sha256 = "10511a54dvafw1jrk75mrhml53c7b7w4yaw7241696lc2hfvr895";
528 sha256 = "1c35qyhvy27q9ih9n899f3h4sdnpgq027dbiilly2qb5cvgarchm";
454 };
529 };
455 meta = {
530 meta = {
456 license = [ pkgs.lib.licenses.mit ];
531 license = [ pkgs.lib.licenses.mit ];
457 };
532 };
458 };
533 };
459 "prompt-toolkit" = super.buildPythonPackage {
534 "prompt-toolkit" = super.buildPythonPackage {
460 name = "prompt-toolkit-1.0.16";
535 name = "prompt-toolkit-1.0.18";
461 doCheck = false;
536 doCheck = false;
462 propagatedBuildInputs = [
537 propagatedBuildInputs = [
463 self."six"
538 self."six"
464 self."wcwidth"
539 self."wcwidth"
465 ];
540 ];
466 src = fetchurl {
541 src = fetchurl {
467 url = "https://files.pythonhosted.org/packages/f1/03/bb36771dc9fa7553ac4bdc639a9ecdf6fda0ff4176faf940d97e3c16e41d/prompt_toolkit-1.0.16.tar.gz";
542 url = "https://files.pythonhosted.org/packages/c5/64/c170e5b1913b540bf0c8ab7676b21fdd1d25b65ddeb10025c6ca43cccd4c/prompt_toolkit-1.0.18.tar.gz";
468 sha256 = "1d65hm6nf0cbq0q0121m60zzy4s1fpg9fn761s1yxf08dridvkn1";
543 sha256 = "09h1153wgr5x2ny7ds0w2m81n3bb9j8hjb8sjfnrg506r01clkyx";
469 };
544 };
470 meta = {
545 meta = {
471 license = [ pkgs.lib.licenses.bsdOriginal ];
546 license = [ pkgs.lib.licenses.bsdOriginal ];
472 };
547 };
473 };
548 };
474 "psutil" = super.buildPythonPackage {
549 "psutil" = super.buildPythonPackage {
475 name = "psutil-5.5.1";
550 name = "psutil-5.6.5";
476 doCheck = false;
551 doCheck = false;
477 src = fetchurl {
552 src = fetchurl {
478 url = "https://files.pythonhosted.org/packages/c7/01/7c30b247cdc5ba29623faa5c8cf1f1bbf7e041783c340414b0ed7e067c64/psutil-5.5.1.tar.gz";
553 url = "https://files.pythonhosted.org/packages/03/9a/95c4b3d0424426e5fd94b5302ff74cea44d5d4f53466e1228ac8e73e14b4/psutil-5.6.5.tar.gz";
479 sha256 = "045qaqvn6k90bj5bcy259yrwcd2afgznaav3sfhphy9b8ambzkkj";
554 sha256 = "0isil5jxwwd8awz54qk28rpgjg43i5l6yl70g40vxwa4r4m56lfh";
480 };
555 };
481 meta = {
556 meta = {
482 license = [ pkgs.lib.licenses.bsdOriginal ];
557 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -494,16 +569,42 b' self: super: {'
494 };
569 };
495 };
570 };
496 "py" = super.buildPythonPackage {
571 "py" = super.buildPythonPackage {
497 name = "py-1.6.0";
572 name = "py-1.8.0";
498 doCheck = false;
573 doCheck = false;
499 src = fetchurl {
574 src = fetchurl {
500 url = "https://files.pythonhosted.org/packages/4f/38/5f427d1eedae73063ce4da680d2bae72014995f9fdeaa57809df61c968cd/py-1.6.0.tar.gz";
575 url = "https://files.pythonhosted.org/packages/f1/5a/87ca5909f400a2de1561f1648883af74345fe96349f34f737cdfc94eba8c/py-1.8.0.tar.gz";
501 sha256 = "1wcs3zv9wl5m5x7p16avqj2gsrviyb23yvc3pr330isqs0sh98q6";
576 sha256 = "0lsy1gajva083pzc7csj1cvbmminb7b4l6a0prdzyb3fd829nqyw";
502 };
577 };
503 meta = {
578 meta = {
504 license = [ pkgs.lib.licenses.mit ];
579 license = [ pkgs.lib.licenses.mit ];
505 };
580 };
506 };
581 };
582 "pycparser" = super.buildPythonPackage {
583 name = "pycparser-2.19";
584 doCheck = false;
585 src = fetchurl {
586 url = "https://files.pythonhosted.org/packages/68/9e/49196946aee219aead1290e00d1e7fdeab8567783e83e1b9ab5585e6206a/pycparser-2.19.tar.gz";
587 sha256 = "1cr5dcj9628lkz1qlwq3fv97c25363qppkmcayqvd05dpy573259";
588 };
589 meta = {
590 license = [ pkgs.lib.licenses.bsdOriginal ];
591 };
592 };
593 "pygit2" = super.buildPythonPackage {
594 name = "pygit2-0.28.2";
595 doCheck = false;
596 propagatedBuildInputs = [
597 self."cffi"
598 self."six"
599 ];
600 src = fetchurl {
601 url = "https://files.pythonhosted.org/packages/4c/64/88c2a4eb2d22ca1982b364f41ff5da42d61de791d7eb68140e7f8f7eb721/pygit2-0.28.2.tar.gz";
602 sha256 = "11kzj5mjkspvplnpdb6bj8dcj6rgmkk986k8hjcklyg5yaxkz32d";
603 };
604 meta = {
605 license = [ { fullName = "GPLv2 with linking exception"; } ];
606 };
607 };
507 "pygments" = super.buildPythonPackage {
608 "pygments" = super.buildPythonPackage {
508 name = "pygments-2.4.2";
609 name = "pygments-2.4.2";
509 doCheck = false;
610 doCheck = false;
@@ -515,6 +616,17 b' self: super: {'
515 license = [ pkgs.lib.licenses.bsdOriginal ];
616 license = [ pkgs.lib.licenses.bsdOriginal ];
516 };
617 };
517 };
618 };
619 "pyparsing" = super.buildPythonPackage {
620 name = "pyparsing-2.4.5";
621 doCheck = false;
622 src = fetchurl {
623 url = "https://files.pythonhosted.org/packages/00/32/8076fa13e832bb4dcff379f18f228e5a53412be0631808b9ca2610c0f566/pyparsing-2.4.5.tar.gz";
624 sha256 = "0fk8gsybiw1gm146mkjdjvaajwh20xwvpv4j7syh2zrnpq0j19jc";
625 };
626 meta = {
627 license = [ pkgs.lib.licenses.mit ];
628 };
629 };
518 "pyramid" = super.buildPythonPackage {
630 "pyramid" = super.buildPythonPackage {
519 name = "pyramid-1.10.4";
631 name = "pyramid-1.10.4";
520 doCheck = false;
632 doCheck = false;
@@ -539,59 +651,61 b' self: super: {'
539 };
651 };
540 };
652 };
541 "pyramid-mako" = super.buildPythonPackage {
653 "pyramid-mako" = super.buildPythonPackage {
542 name = "pyramid-mako-1.0.2";
654 name = "pyramid-mako-1.1.0";
543 doCheck = false;
655 doCheck = false;
544 propagatedBuildInputs = [
656 propagatedBuildInputs = [
545 self."pyramid"
657 self."pyramid"
546 self."mako"
658 self."mako"
547 ];
659 ];
548 src = fetchurl {
660 src = fetchurl {
549 url = "https://files.pythonhosted.org/packages/f1/92/7e69bcf09676d286a71cb3bbb887b16595b96f9ba7adbdc239ffdd4b1eb9/pyramid_mako-1.0.2.tar.gz";
661 url = "https://files.pythonhosted.org/packages/63/7b/5e2af68f675071a6bad148c1c393928f0ef5fcd94e95cbf53b89d6471a83/pyramid_mako-1.1.0.tar.gz";
550 sha256 = "18gk2vliq8z4acblsl6yzgbvnr9rlxjlcqir47km7kvlk1xri83d";
662 sha256 = "1qj0m091mnii86j2q1d82yir22nha361rvhclvg3s70z8iiwhrh0";
551 };
663 };
552 meta = {
664 meta = {
553 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
665 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
554 };
666 };
555 };
667 };
556 "pytest" = super.buildPythonPackage {
668 "pytest" = super.buildPythonPackage {
557 name = "pytest-3.8.2";
669 name = "pytest-4.6.5";
558 doCheck = false;
670 doCheck = false;
559 propagatedBuildInputs = [
671 propagatedBuildInputs = [
560 self."py"
672 self."py"
561 self."six"
673 self."six"
562 self."setuptools"
674 self."packaging"
563 self."attrs"
675 self."attrs"
564 self."more-itertools"
565 self."atomicwrites"
676 self."atomicwrites"
566 self."pluggy"
677 self."pluggy"
678 self."importlib-metadata"
679 self."wcwidth"
567 self."funcsigs"
680 self."funcsigs"
568 self."pathlib2"
681 self."pathlib2"
682 self."more-itertools"
569 ];
683 ];
570 src = fetchurl {
684 src = fetchurl {
571 url = "https://files.pythonhosted.org/packages/5f/d2/7f77f406ac505abda02ab4afb50d06ebf304f6ea42fca34f8f37529106b2/pytest-3.8.2.tar.gz";
685 url = "https://files.pythonhosted.org/packages/2a/c6/1d1f32f6a5009900521b12e6560fb6b7245b0d4bc3fb771acd63d10e30e1/pytest-4.6.5.tar.gz";
572 sha256 = "18nrwzn61kph2y6gxwfz9ms68rfvr9d4vcffsxng9p7jk9z18clk";
686 sha256 = "0iykwwfp4h181nd7rsihh2120b0rkawlw7rvbl19sgfspncr3hwg";
573 };
687 };
574 meta = {
688 meta = {
575 license = [ pkgs.lib.licenses.mit ];
689 license = [ pkgs.lib.licenses.mit ];
576 };
690 };
577 };
691 };
578 "pytest-cov" = super.buildPythonPackage {
692 "pytest-cov" = super.buildPythonPackage {
579 name = "pytest-cov-2.6.0";
693 name = "pytest-cov-2.7.1";
580 doCheck = false;
694 doCheck = false;
581 propagatedBuildInputs = [
695 propagatedBuildInputs = [
582 self."pytest"
696 self."pytest"
583 self."coverage"
697 self."coverage"
584 ];
698 ];
585 src = fetchurl {
699 src = fetchurl {
586 url = "https://files.pythonhosted.org/packages/d9/e2/58f90a316fbd94dd50bf5c826a23f3f5d079fb3cc448c1e9f0e3c33a3d2a/pytest-cov-2.6.0.tar.gz";
700 url = "https://files.pythonhosted.org/packages/bb/0f/3db7ff86801883b21d5353b258c994b1b8e2abbc804e2273b8d0fd19004b/pytest-cov-2.7.1.tar.gz";
587 sha256 = "0qnpp9y3ygx4jk4pf5ad71fh2skbvnr6gl54m7rg5qysnx4g0q73";
701 sha256 = "0filvmmyqm715azsl09ql8hy2x7h286n6d8z5x42a1wpvvys83p0";
588 };
702 };
589 meta = {
703 meta = {
590 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
704 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
591 };
705 };
592 };
706 };
593 "pytest-profiling" = super.buildPythonPackage {
707 "pytest-profiling" = super.buildPythonPackage {
594 name = "pytest-profiling-1.3.0";
708 name = "pytest-profiling-1.7.0";
595 doCheck = false;
709 doCheck = false;
596 propagatedBuildInputs = [
710 propagatedBuildInputs = [
597 self."six"
711 self."six"
@@ -599,53 +713,65 b' self: super: {'
599 self."gprof2dot"
713 self."gprof2dot"
600 ];
714 ];
601 src = fetchurl {
715 src = fetchurl {
602 url = "https://files.pythonhosted.org/packages/f5/34/4626126e041a51ef50a80d0619519b18d20aef249aac25b0d0fdd47e57ee/pytest-profiling-1.3.0.tar.gz";
716 url = "https://files.pythonhosted.org/packages/39/70/22a4b33739f07f1732a63e33bbfbf68e0fa58cfba9d200e76d01921eddbf/pytest-profiling-1.7.0.tar.gz";
603 sha256 = "08r5afx5z22yvpmsnl91l4amsy1yxn8qsmm61mhp06mz8zjs51kb";
717 sha256 = "0abz9gi26jpcfdzgsvwad91555lpgdc8kbymicmms8k2fqa8z4wk";
604 };
718 };
605 meta = {
719 meta = {
606 license = [ pkgs.lib.licenses.mit ];
720 license = [ pkgs.lib.licenses.mit ];
607 };
721 };
608 };
722 };
609 "pytest-runner" = super.buildPythonPackage {
723 "pytest-runner" = super.buildPythonPackage {
610 name = "pytest-runner-4.2";
724 name = "pytest-runner-5.1";
611 doCheck = false;
725 doCheck = false;
612 src = fetchurl {
726 src = fetchurl {
613 url = "https://files.pythonhosted.org/packages/9e/b7/fe6e8f87f9a756fd06722216f1b6698ccba4d269eac6329d9f0c441d0f93/pytest-runner-4.2.tar.gz";
727 url = "https://files.pythonhosted.org/packages/d9/6d/4b41a74b31720e25abd4799be72d54811da4b4d0233e38b75864dcc1f7ad/pytest-runner-5.1.tar.gz";
614 sha256 = "1gkpyphawxz38ni1gdq1fmwyqcg02m7ypzqvv46z06crwdxi2gyj";
728 sha256 = "0ykfcnpp8c22winj63qzc07l5axwlc9ikl8vn05sc32gv3417815";
615 };
729 };
616 meta = {
730 meta = {
617 license = [ pkgs.lib.licenses.mit ];
731 license = [ pkgs.lib.licenses.mit ];
618 };
732 };
619 };
733 };
620 "pytest-sugar" = super.buildPythonPackage {
734 "pytest-sugar" = super.buildPythonPackage {
621 name = "pytest-sugar-0.9.1";
735 name = "pytest-sugar-0.9.2";
622 doCheck = false;
736 doCheck = false;
623 propagatedBuildInputs = [
737 propagatedBuildInputs = [
624 self."pytest"
738 self."pytest"
625 self."termcolor"
739 self."termcolor"
740 self."packaging"
626 ];
741 ];
627 src = fetchurl {
742 src = fetchurl {
628 url = "https://files.pythonhosted.org/packages/3e/6a/a3f909083079d03bde11d06ab23088886bbe25f2c97fbe4bb865e2bf05bc/pytest-sugar-0.9.1.tar.gz";
743 url = "https://files.pythonhosted.org/packages/55/59/f02f78d1c80f7e03e23177f60624c8106d4f23d124c921df103f65692464/pytest-sugar-0.9.2.tar.gz";
629 sha256 = "0b4av40dv30727m54v211r0nzwjp2ajkjgxix6j484qjmwpw935b";
744 sha256 = "1asq7yc4g8bx2sn7yy974mhc9ywvaihasjab4inkirdwn9s7mn7w";
630 };
745 };
631 meta = {
746 meta = {
632 license = [ pkgs.lib.licenses.bsdOriginal ];
747 license = [ pkgs.lib.licenses.bsdOriginal ];
633 };
748 };
634 };
749 };
635 "pytest-timeout" = super.buildPythonPackage {
750 "pytest-timeout" = super.buildPythonPackage {
636 name = "pytest-timeout-1.3.2";
751 name = "pytest-timeout-1.3.3";
637 doCheck = false;
752 doCheck = false;
638 propagatedBuildInputs = [
753 propagatedBuildInputs = [
639 self."pytest"
754 self."pytest"
640 ];
755 ];
641 src = fetchurl {
756 src = fetchurl {
642 url = "https://files.pythonhosted.org/packages/8c/3e/1b6a319d12ae7baa3acb7c18ff2c8630a09471a0319d43535c683b4d03eb/pytest-timeout-1.3.2.tar.gz";
757 url = "https://files.pythonhosted.org/packages/13/48/7a166eaa29c1dca6cc253e3ba5773ff2e4aa4f567c1ea3905808e95ac5c1/pytest-timeout-1.3.3.tar.gz";
643 sha256 = "09wnmzvnls2mnsdz7x3c3sk2zdp6jl4dryvyj5i8hqz16q2zq5qi";
758 sha256 = "1cczcjhw4xx5sjkhxlhc5c1bkr7x6fcyx12wrnvwfckshdvblc2a";
644 };
759 };
645 meta = {
760 meta = {
646 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
761 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
647 };
762 };
648 };
763 };
764 "redis" = super.buildPythonPackage {
765 name = "redis-3.3.11";
766 doCheck = false;
767 src = fetchurl {
768 url = "https://files.pythonhosted.org/packages/06/ca/00557c74279d2f256d3c42cabf237631355f3a132e4c74c2000e6647ad98/redis-3.3.11.tar.gz";
769 sha256 = "1hicqbi5xl92hhml82awrr2rxl9jar5fp8nbcycj9qgmsdwc43wd";
770 };
771 meta = {
772 license = [ pkgs.lib.licenses.mit ];
773 };
774 };
649 "repoze.lru" = super.buildPythonPackage {
775 "repoze.lru" = super.buildPythonPackage {
650 name = "repoze.lru-0.7";
776 name = "repoze.lru-0.7";
651 doCheck = false;
777 doCheck = false;
@@ -658,7 +784,7 b' self: super: {'
658 };
784 };
659 };
785 };
660 "rhodecode-vcsserver" = super.buildPythonPackage {
786 "rhodecode-vcsserver" = super.buildPythonPackage {
661 name = "rhodecode-vcsserver-4.17.4";
787 name = "rhodecode-vcsserver-4.18.0";
662 buildInputs = [
788 buildInputs = [
663 self."pytest"
789 self."pytest"
664 self."py"
790 self."py"
@@ -691,7 +817,9 b' self: super: {'
691 self."pastedeploy"
817 self."pastedeploy"
692 self."pyramid"
818 self."pyramid"
693 self."pyramid-mako"
819 self."pyramid-mako"
820 self."pygit2"
694 self."repoze.lru"
821 self."repoze.lru"
822 self."redis"
695 self."simplejson"
823 self."simplejson"
696 self."subprocess32"
824 self."subprocess32"
697 self."subvertpy"
825 self."subvertpy"
@@ -748,11 +876,11 b' self: super: {'
748 };
876 };
749 };
877 };
750 "setuptools" = super.buildPythonPackage {
878 "setuptools" = super.buildPythonPackage {
751 name = "setuptools-41.0.1";
879 name = "setuptools-44.0.0";
752 doCheck = false;
880 doCheck = false;
753 src = fetchurl {
881 src = fetchurl {
754 url = "https://files.pythonhosted.org/packages/1d/64/a18a487b4391a05b9c7f938b94a16d80305bf0369c6b0b9509e86165e1d3/setuptools-41.0.1.zip";
882 url = "https://files.pythonhosted.org/packages/b0/f3/44da7482ac6da3f36f68e253cb04de37365b3dba9036a3c70773b778b485/setuptools-44.0.0.zip";
755 sha256 = "04sns22y2hhsrwfy1mha2lgslvpjsjsz8xws7h2rh5a7ylkd28m2";
883 sha256 = "025h5cnxcmda1893l6i12hrwdvs1n8r31qs6q4pkif2v7rrggfp5";
756 };
884 };
757 meta = {
885 meta = {
758 license = [ pkgs.lib.licenses.mit ];
886 license = [ pkgs.lib.licenses.mit ];
@@ -825,7 +953,7 b' self: super: {'
825 };
953 };
826 };
954 };
827 "traitlets" = super.buildPythonPackage {
955 "traitlets" = super.buildPythonPackage {
828 name = "traitlets-4.3.2";
956 name = "traitlets-4.3.3";
829 doCheck = false;
957 doCheck = false;
830 propagatedBuildInputs = [
958 propagatedBuildInputs = [
831 self."ipython-genutils"
959 self."ipython-genutils"
@@ -834,8 +962,8 b' self: super: {'
834 self."enum34"
962 self."enum34"
835 ];
963 ];
836 src = fetchurl {
964 src = fetchurl {
837 url = "https://files.pythonhosted.org/packages/a5/98/7f5ef2fe9e9e071813aaf9cb91d1a732e0a68b6c44a32b38cb8e14c3f069/traitlets-4.3.2.tar.gz";
965 url = "https://files.pythonhosted.org/packages/75/b0/43deb021bc943f18f07cbe3dac1d681626a48997b7ffa1e7fb14ef922b21/traitlets-4.3.3.tar.gz";
838 sha256 = "0dbq7sx26xqz5ixs711k5nc88p8a0nqyz6162pwks5dpcz9d4jww";
966 sha256 = "1xsrwgivpkxlbr4dfndfsi098s29yqgswgjc1qqn69yxklvfw8yh";
839 };
967 };
840 meta = {
968 meta = {
841 license = [ pkgs.lib.licenses.bsdOriginal ];
969 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -864,11 +992,11 b' self: super: {'
864 };
992 };
865 };
993 };
866 "waitress" = super.buildPythonPackage {
994 "waitress" = super.buildPythonPackage {
867 name = "waitress-1.3.0";
995 name = "waitress-1.3.1";
868 doCheck = false;
996 doCheck = false;
869 src = fetchurl {
997 src = fetchurl {
870 url = "https://files.pythonhosted.org/packages/43/50/9890471320d5ad22761ae46661cf745f487b1c8c4ec49352b99e1078b970/waitress-1.3.0.tar.gz";
998 url = "https://files.pythonhosted.org/packages/a6/e6/708da7bba65898e5d759ade8391b1077e49d07be0b0223c39f5be04def56/waitress-1.3.1.tar.gz";
871 sha256 = "09j5dzbbcxib7vdskhx39s1qsydlr4n2p2png71d7mjnr9pnwajf";
999 sha256 = "1iysl8ka3l4cdrr0r19fh1cv28q41mwpvgsb81ji7k4shkb0k3i7";
872 };
1000 };
873 meta = {
1001 meta = {
874 license = [ pkgs.lib.licenses.zpl21 ];
1002 license = [ pkgs.lib.licenses.zpl21 ];
@@ -913,6 +1041,20 b' self: super: {'
913 license = [ pkgs.lib.licenses.mit ];
1041 license = [ pkgs.lib.licenses.mit ];
914 };
1042 };
915 };
1043 };
1044 "zipp" = super.buildPythonPackage {
1045 name = "zipp-0.6.0";
1046 doCheck = false;
1047 propagatedBuildInputs = [
1048 self."more-itertools"
1049 ];
1050 src = fetchurl {
1051 url = "https://files.pythonhosted.org/packages/57/dd/585d728479d97d25aeeb9aa470d36a4ad8d0ba5610f84e14770128ce6ff7/zipp-0.6.0.tar.gz";
1052 sha256 = "13ndkf7vklw978a4gdl1yfvn8hch28429a0iam67sg4nrp5v261p";
1053 };
1054 meta = {
1055 license = [ pkgs.lib.licenses.mit ];
1056 };
1057 };
916 "zope.deprecation" = super.buildPythonPackage {
1058 "zope.deprecation" = super.buildPythonPackage {
917 name = "zope.deprecation-4.4.0";
1059 name = "zope.deprecation-4.4.0";
918 doCheck = false;
1060 doCheck = false;
@@ -23,6 +23,7 b' pkgs.stdenv.mkDerivation {'
23 pythonPackages.pip-tools
23 pythonPackages.pip-tools
24 pkgs.apr
24 pkgs.apr
25 pkgs.aprutil
25 pkgs.aprutil
26 pkgs.libffi
26 ];
27 ];
27
28
28 shellHook = ''
29 shellHook = ''
@@ -3,22 +3,24 b''
3 # our custom configobj
3 # our custom configobj
4 https://code.rhodecode.com/upstream/configobj/artifacts/download/0-012de99a-b1e1-4f64-a5c0-07a98a41b324.tar.gz?md5=6a513f51fe04b2c18cf84c1395a7c626#egg=configobj==5.0.6
4 https://code.rhodecode.com/upstream/configobj/artifacts/download/0-012de99a-b1e1-4f64-a5c0-07a98a41b324.tar.gz?md5=6a513f51fe04b2c18cf84c1395a7c626#egg=configobj==5.0.6
5
5
6 dogpile.cache==0.7.1
6 dogpile.cache==0.9.0
7 dogpile.core==0.4.1
7 dogpile.core==0.4.1
8 decorator==4.1.2
8 decorator==4.1.2
9 dulwich==0.13.0
9 dulwich==0.13.0
10 hgsubversion==1.9.3
10 hgsubversion==1.9.3
11 hg-evolve==8.5.1
11 hg-evolve==9.1.0
12 mako==1.0.7
12 mako==1.1.0
13 markupsafe==1.1.0
13 markupsafe==1.1.1
14 mercurial==4.9.1
14 mercurial==5.1.1
15 msgpack-python==0.5.6
15 msgpack-python==0.5.6
16
16
17 pastedeploy==2.0.1
17 pastedeploy==2.0.1
18 pyramid==1.10.4
18 pyramid==1.10.4
19 pyramid-mako==1.0.2
19 pyramid-mako==1.1.0
20 pygit2==0.28.2
20
21
21 repoze.lru==0.7
22 repoze.lru==0.7
23 redis==3.3.11
22 simplejson==3.16.0
24 simplejson==3.16.0
23 subprocess32==3.5.4
25 subprocess32==3.5.4
24 subvertpy==0.10.1
26 subvertpy==0.10.1
@@ -33,7 +35,7 b' zope.interface==4.6.0'
33 gevent==1.4.0
35 gevent==1.4.0
34 greenlet==0.4.15
36 greenlet==0.4.15
35 gunicorn==19.9.0
37 gunicorn==19.9.0
36 waitress==1.3.0
38 waitress==1.3.1
37
39
38 ## debug
40 ## debug
39 ipdb==0.12.0
41 ipdb==0.12.0
@@ -41,3 +43,6 b' ipython==5.1.0'
41
43
42 ## test related requirements
44 ## test related requirements
43 -r requirements_test.txt
45 -r requirements_test.txt
46
47 ## uncomment to add the debug libraries
48 #-r requirements_debug.txt
@@ -1,12 +1,18 b''
1 # contains not directly required libraries we want to pin the version.
1 # contains not directly required libraries we want to pin the version.
2
2
3 atomicwrites==1.2.1
3 atomicwrites==1.3.0
4 attrs==18.2.0
4 attrs==19.3.0
5 hupper==1.6.1
5 contextlib2==0.6.0.post1
6 pathlib2==2.3.4
6 cffi==1.12.3
7 hupper==1.9.1
8 importlib-metadata==0.23
9 packaging==19.2.0
10 pathlib2==2.3.5
7 pygments==2.4.2
11 pygments==2.4.2
8 psutil==5.5.1
12 pyparsing==2.4.5
9 pluggy==0.11.0
13 psutil==5.6.5
14 pluggy==0.13.1
10 scandir==1.10.0
15 scandir==1.10.0
11 setproctitle==1.1.10
16 setproctitle==1.1.10
12 venusian==1.2.0
17 venusian==1.2.0
18 wcwidth==0.1.7
@@ -1,16 +1,16 b''
1 # test related requirements
1 # test related requirements
2 pytest==3.8.2
2 pytest==4.6.5
3 py==1.6.0
3 py==1.8.0
4 pytest-cov==2.6.0
4 pytest-cov==2.7.1
5 pytest-sugar==0.9.1
5 pytest-sugar==0.9.2
6 pytest-runner==4.2.0
6 pytest-runner==5.1.0
7 pytest-profiling==1.3.0
7 pytest-profiling==1.7.0
8 pytest-timeout==1.3.2
8 pytest-timeout==1.3.3
9 gprof2dot==2017.9.19
9 gprof2dot==2017.9.19
10
10
11 mock==1.0.1
11 mock==3.0.5
12 cov-core==1.15.0
12 cov-core==1.15.0
13 coverage==4.5.3
13 coverage==4.5.4
14
14
15 webtest==2.0.33
15 webtest==2.0.33
16 beautifulsoup4==4.6.3
16 beautifulsoup4==4.6.3
@@ -1,1 +1,1 b''
1 4.17.4 No newline at end of file
1 4.18.0 No newline at end of file
@@ -44,25 +44,7 b' class RepoFactory(object):'
44 raise NotImplementedError()
44 raise NotImplementedError()
45
45
46 def repo(self, wire, create=False):
46 def repo(self, wire, create=False):
47 """
47 raise NotImplementedError()
48 Get a repository instance for the given path.
49
50 Uses internally the low level beaker API since the decorators introduce
51 significant overhead.
52 """
53 region = self._cache_region
54 context = wire.get('context', None)
55 repo_path = wire.get('path', '')
56 context_uid = '{}'.format(context)
57 cache = wire.get('cache', True)
58 cache_on = context and cache
59
60 @region.conditional_cache_on_arguments(condition=cache_on)
61 def create_new_repo(_repo_type, _repo_path, _context_uid):
62 return self._create_repo(wire, create)
63
64 repo = create_new_repo(self.repo_type, repo_path, context_uid)
65 return repo
66
48
67
49
68 def obfuscate_qs(query_string):
50 def obfuscate_qs(query_string):
This diff has been collapsed as it changes many lines, (683 lines changed) Show them Hide them
@@ -14,6 +14,7 b''
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17 import collections
18 import collections
18 import logging
19 import logging
19 import os
20 import os
@@ -26,42 +27,54 b' import urllib2'
26 from functools import wraps
27 from functools import wraps
27
28
28 import more_itertools
29 import more_itertools
30 import pygit2
31 from pygit2 import Repository as LibGit2Repo
29 from dulwich import index, objects
32 from dulwich import index, objects
30 from dulwich.client import HttpGitClient, LocalGitClient
33 from dulwich.client import HttpGitClient, LocalGitClient
31 from dulwich.errors import (
34 from dulwich.errors import (
32 NotGitRepository, ChecksumMismatch, WrongObjectException,
35 NotGitRepository, ChecksumMismatch, WrongObjectException,
33 MissingCommitError, ObjectMissing, HangupException,
36 MissingCommitError, ObjectMissing, HangupException,
34 UnexpectedCommandError)
37 UnexpectedCommandError)
35 from dulwich.repo import Repo as DulwichRepo, Tag
38 from dulwich.repo import Repo as DulwichRepo
36 from dulwich.server import update_server_info
39 from dulwich.server import update_server_info
37
40
38 from vcsserver import exceptions, settings, subprocessio
41 from vcsserver import exceptions, settings, subprocessio
39 from vcsserver.utils import safe_str
42 from vcsserver.utils import safe_str, safe_int
40 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original
43 from vcsserver.base import RepoFactory, obfuscate_qs
41 from vcsserver.hgcompat import (
44 from vcsserver.hgcompat import (
42 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
45 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler)
43 from vcsserver.git_lfs.lib import LFSOidStore
46 from vcsserver.git_lfs.lib import LFSOidStore
47 from vcsserver.vcs_base import RemoteBase
44
48
45 DIR_STAT = stat.S_IFDIR
49 DIR_STAT = stat.S_IFDIR
46 FILE_MODE = stat.S_IFMT
50 FILE_MODE = stat.S_IFMT
47 GIT_LINK = objects.S_IFGITLINK
51 GIT_LINK = objects.S_IFGITLINK
52 PEELED_REF_MARKER = '^{}'
53
48
54
49 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
50
56
51
57
58 def str_to_dulwich(value):
59 """
60 Dulwich 0.10.1a requires `unicode` objects to be passed in.
61 """
62 return value.decode(settings.WIRE_ENCODING)
63
64
52 def reraise_safe_exceptions(func):
65 def reraise_safe_exceptions(func):
53 """Converts Dulwich exceptions to something neutral."""
66 """Converts Dulwich exceptions to something neutral."""
67
54 @wraps(func)
68 @wraps(func)
55 def wrapper(*args, **kwargs):
69 def wrapper(*args, **kwargs):
56 try:
70 try:
57 return func(*args, **kwargs)
71 return func(*args, **kwargs)
58 except (ChecksumMismatch, WrongObjectException, MissingCommitError,
72 except (ChecksumMismatch, WrongObjectException, MissingCommitError, ObjectMissing,) as e:
59 ObjectMissing) as e:
73 exc = exceptions.LookupException(org_exc=e)
60 exc = exceptions.LookupException(e)
74 raise exc(safe_str(e))
61 raise exc(e)
62 except (HangupException, UnexpectedCommandError) as e:
75 except (HangupException, UnexpectedCommandError) as e:
63 exc = exceptions.VcsException(e)
76 exc = exceptions.VcsException(org_exc=e)
64 raise exc(e)
77 raise exc(safe_str(e))
65 except Exception as e:
78 except Exception as e:
66 # NOTE(marcink): becuase of how dulwich handles some exceptions
79 # NOTE(marcink): becuase of how dulwich handles some exceptions
67 # (KeyError on empty repos), we cannot track this and catch all
80 # (KeyError on empty repos), we cannot track this and catch all
@@ -80,33 +93,51 b' class Repo(DulwichRepo):'
80 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
93 Since dulwich is sometimes keeping .idx file descriptors open, it leads to
81 "Too many open files" error. We need to close all opened file descriptors
94 "Too many open files" error. We need to close all opened file descriptors
82 once the repo object is destroyed.
95 once the repo object is destroyed.
83
84 TODO: mikhail: please check if we need this wrapper after updating dulwich
85 to 0.12.0 +
86 """
96 """
87 def __del__(self):
97 def __del__(self):
88 if hasattr(self, 'object_store'):
98 if hasattr(self, 'object_store'):
89 self.close()
99 self.close()
90
100
91
101
102 class Repository(LibGit2Repo):
103
104 def __enter__(self):
105 return self
106
107 def __exit__(self, exc_type, exc_val, exc_tb):
108 self.free()
109
110
92 class GitFactory(RepoFactory):
111 class GitFactory(RepoFactory):
93 repo_type = 'git'
112 repo_type = 'git'
94
113
95 def _create_repo(self, wire, create):
114 def _create_repo(self, wire, create, use_libgit2=False):
115 if use_libgit2:
116 return Repository(wire['path'])
117 else:
96 repo_path = str_to_dulwich(wire['path'])
118 repo_path = str_to_dulwich(wire['path'])
97 return Repo(repo_path)
119 return Repo(repo_path)
98
120
121 def repo(self, wire, create=False, use_libgit2=False):
122 """
123 Get a repository instance for the given path.
124 """
125 return self._create_repo(wire, create, use_libgit2)
99
126
100 class GitRemote(object):
127 def repo_libgit2(self, wire):
128 return self.repo(wire, use_libgit2=True)
129
130
131 class GitRemote(RemoteBase):
101
132
102 def __init__(self, factory):
133 def __init__(self, factory):
103 self._factory = factory
134 self._factory = factory
104 self.peeled_ref_marker = '^{}'
105 self._bulk_methods = {
135 self._bulk_methods = {
106 "author": self.commit_attribute,
136 "date": self.date,
107 "date": self.get_object_attrs,
137 "author": self.author,
108 "message": self.commit_attribute,
138 "branch": self.branch,
109 "parents": self.commit_attribute,
139 "message": self.message,
140 "parents": self.parents,
110 "_commit": self.revision,
141 "_commit": self.revision,
111 }
142 }
112
143
@@ -115,10 +146,6 b' class GitRemote(object):'
115 return dict([(x[0] + '_' + x[1], x[2]) for x in wire['config']])
146 return dict([(x[0] + '_' + x[1], x[2]) for x in wire['config']])
116 return {}
147 return {}
117
148
118 def _assign_ref(self, wire, ref, commit_id):
119 repo = self._factory.repo(wire)
120 repo[ref] = commit_id
121
122 def _remote_conf(self, config):
149 def _remote_conf(self, config):
123 params = [
150 params = [
124 '-c', 'core.askpass=""',
151 '-c', 'core.askpass=""',
@@ -129,49 +156,75 b' class GitRemote(object):'
129 return params
156 return params
130
157
131 @reraise_safe_exceptions
158 @reraise_safe_exceptions
159 def discover_git_version(self):
160 stdout, _ = self.run_git_command(
161 {}, ['--version'], _bare=True, _safe=True)
162 prefix = 'git version'
163 if stdout.startswith(prefix):
164 stdout = stdout[len(prefix):]
165 return stdout.strip()
166
167 @reraise_safe_exceptions
132 def is_empty(self, wire):
168 def is_empty(self, wire):
133 repo = self._factory.repo(wire)
169 repo_init = self._factory.repo_libgit2(wire)
170 with repo_init as repo:
171
134 try:
172 try:
135 return not repo.head()
173 has_head = repo.head.name
174 if has_head:
175 return False
176
177 # NOTE(marcink): check again using more expensive method
178 return repo.is_empty
136 except Exception:
179 except Exception:
137 log.exception("failed to read object_store")
180 pass
181
138 return True
182 return True
139
183
140 @reraise_safe_exceptions
184 @reraise_safe_exceptions
141 def add_object(self, wire, content):
142 repo = self._factory.repo(wire)
143 blob = objects.Blob()
144 blob.set_raw_string(content)
145 repo.object_store.add_object(blob)
146 return blob.id
147
148 @reraise_safe_exceptions
149 def assert_correct_path(self, wire):
185 def assert_correct_path(self, wire):
150 path = wire.get('path')
186 cache_on, context_uid, repo_id = self._cache_on(wire)
187 @self.region.conditional_cache_on_arguments(condition=cache_on)
188 def _assert_correct_path(_context_uid, _repo_id):
151 try:
189 try:
152 self._factory.repo(wire)
190 repo_init = self._factory.repo_libgit2(wire)
153 except NotGitRepository as e:
191 with repo_init as repo:
192 pass
193 except pygit2.GitError:
194 path = wire.get('path')
154 tb = traceback.format_exc()
195 tb = traceback.format_exc()
155 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
196 log.debug("Invalid Git path `%s`, tb: %s", path, tb)
156 return False
197 return False
157
198
158 return True
199 return True
200 return _assert_correct_path(context_uid, repo_id)
159
201
160 @reraise_safe_exceptions
202 @reraise_safe_exceptions
161 def bare(self, wire):
203 def bare(self, wire):
162 repo = self._factory.repo(wire)
204 repo_init = self._factory.repo_libgit2(wire)
163 return repo.bare
205 with repo_init as repo:
206 return repo.is_bare
164
207
165 @reraise_safe_exceptions
208 @reraise_safe_exceptions
166 def blob_as_pretty_string(self, wire, sha):
209 def blob_as_pretty_string(self, wire, sha):
167 repo = self._factory.repo(wire)
210 repo_init = self._factory.repo_libgit2(wire)
168 return repo[sha].as_pretty_string()
211 with repo_init as repo:
212 blob_obj = repo[sha]
213 blob = blob_obj.data
214 return blob
169
215
170 @reraise_safe_exceptions
216 @reraise_safe_exceptions
171 def blob_raw_length(self, wire, sha):
217 def blob_raw_length(self, wire, sha):
172 repo = self._factory.repo(wire)
218 cache_on, context_uid, repo_id = self._cache_on(wire)
219 @self.region.conditional_cache_on_arguments(condition=cache_on)
220 def _blob_raw_length(_repo_id, _sha):
221
222 repo_init = self._factory.repo_libgit2(wire)
223 with repo_init as repo:
173 blob = repo[sha]
224 blob = repo[sha]
174 return blob.raw_length()
225 return blob.size
226
227 return _blob_raw_length(repo_id, sha)
175
228
176 def _parse_lfs_pointer(self, raw_content):
229 def _parse_lfs_pointer(self, raw_content):
177
230
@@ -191,19 +244,44 b' class GitRemote(object):'
191 return {}
244 return {}
192
245
193 @reraise_safe_exceptions
246 @reraise_safe_exceptions
194 def is_large_file(self, wire, sha):
247 def is_large_file(self, wire, commit_id):
195 repo = self._factory.repo(wire)
248 cache_on, context_uid, repo_id = self._cache_on(wire)
196 blob = repo[sha]
249
197 return self._parse_lfs_pointer(blob.as_raw_string())
250 @self.region.conditional_cache_on_arguments(condition=cache_on)
251 def _is_large_file(_repo_id, _sha):
252 repo_init = self._factory.repo_libgit2(wire)
253 with repo_init as repo:
254 blob = repo[commit_id]
255 if blob.is_binary:
256 return {}
257
258 return self._parse_lfs_pointer(blob.data)
259
260 return _is_large_file(repo_id, commit_id)
261
262 @reraise_safe_exceptions
263 def is_binary(self, wire, tree_id):
264 cache_on, context_uid, repo_id = self._cache_on(wire)
265
266 @self.region.conditional_cache_on_arguments(condition=cache_on)
267 def _is_binary(_repo_id, _tree_id):
268 repo_init = self._factory.repo_libgit2(wire)
269 with repo_init as repo:
270 blob_obj = repo[tree_id]
271 return blob_obj.is_binary
272
273 return _is_binary(repo_id, tree_id)
198
274
199 @reraise_safe_exceptions
275 @reraise_safe_exceptions
200 def in_largefiles_store(self, wire, oid):
276 def in_largefiles_store(self, wire, oid):
201 repo = self._factory.repo(wire)
202 conf = self._wire_to_config(wire)
277 conf = self._wire_to_config(wire)
278 repo_init = self._factory.repo_libgit2(wire)
279 with repo_init as repo:
280 repo_name = repo.path
203
281
204 store_location = conf.get('vcs_git_lfs_store_location')
282 store_location = conf.get('vcs_git_lfs_store_location')
205 if store_location:
283 if store_location:
206 repo_name = repo.path
284
207 store = LFSOidStore(
285 store = LFSOidStore(
208 oid=oid, repo=repo_name, store_location=store_location)
286 oid=oid, repo=repo_name, store_location=store_location)
209 return store.has_oid()
287 return store.has_oid()
@@ -212,12 +290,13 b' class GitRemote(object):'
212
290
213 @reraise_safe_exceptions
291 @reraise_safe_exceptions
214 def store_path(self, wire, oid):
292 def store_path(self, wire, oid):
215 repo = self._factory.repo(wire)
216 conf = self._wire_to_config(wire)
293 conf = self._wire_to_config(wire)
294 repo_init = self._factory.repo_libgit2(wire)
295 with repo_init as repo:
296 repo_name = repo.path
217
297
218 store_location = conf.get('vcs_git_lfs_store_location')
298 store_location = conf.get('vcs_git_lfs_store_location')
219 if store_location:
299 if store_location:
220 repo_name = repo.path
221 store = LFSOidStore(
300 store = LFSOidStore(
222 oid=oid, repo=repo_name, store_location=store_location)
301 oid=oid, repo=repo_name, store_location=store_location)
223 return store.oid_path
302 return store.oid_path
@@ -225,21 +304,22 b' class GitRemote(object):'
225
304
226 @reraise_safe_exceptions
305 @reraise_safe_exceptions
227 def bulk_request(self, wire, rev, pre_load):
306 def bulk_request(self, wire, rev, pre_load):
307 cache_on, context_uid, repo_id = self._cache_on(wire)
308 @self.region.conditional_cache_on_arguments(condition=cache_on)
309 def _bulk_request(_repo_id, _rev, _pre_load):
228 result = {}
310 result = {}
229 for attr in pre_load:
311 for attr in pre_load:
230 try:
312 try:
231 method = self._bulk_methods[attr]
313 method = self._bulk_methods[attr]
232 args = [wire, rev]
314 args = [wire, rev]
233 if attr == "date":
234 args.extend(["commit_time", "commit_timezone"])
235 elif attr in ["author", "message", "parents"]:
236 args.append(attr)
237 result[attr] = method(*args)
315 result[attr] = method(*args)
238 except KeyError as e:
316 except KeyError as e:
239 raise exceptions.VcsException(e)(
317 raise exceptions.VcsException(e)(
240 "Unknown bulk attribute: %s" % attr)
318 "Unknown bulk attribute: %s" % attr)
241 return result
319 return result
242
320
321 return _bulk_request(repo_id, rev, sorted(pre_load))
322
243 def _build_opener(self, url):
323 def _build_opener(self, url):
244 handlers = []
324 handlers = []
245 url_obj = url_parser(url)
325 url_obj = url_parser(url)
@@ -255,6 +335,14 b' class GitRemote(object):'
255
335
256 return urllib2.build_opener(*handlers)
336 return urllib2.build_opener(*handlers)
257
337
338 def _type_id_to_name(self, type_id):
339 return {
340 1: b'commit',
341 2: b'tree',
342 3: b'blob',
343 4: b'tag'
344 }[type_id]
345
258 @reraise_safe_exceptions
346 @reraise_safe_exceptions
259 def check_url(self, url, config):
347 def check_url(self, url, config):
260 url_obj = url_parser(url)
348 url_obj = url_parser(url)
@@ -317,6 +405,42 b' class GitRemote(object):'
317 index.build_index_from_tree(repo.path, repo.index_path(),
405 index.build_index_from_tree(repo.path, repo.index_path(),
318 repo.object_store, repo["HEAD"].tree)
406 repo.object_store, repo["HEAD"].tree)
319
407
408 @reraise_safe_exceptions
409 def branch(self, wire, commit_id):
410 cache_on, context_uid, repo_id = self._cache_on(wire)
411 @self.region.conditional_cache_on_arguments(condition=cache_on)
412 def _branch(_context_uid, _repo_id, _commit_id):
413 regex = re.compile('^refs/heads')
414
415 def filter_with(ref):
416 return regex.match(ref[0]) and ref[1] == _commit_id
417
418 branches = filter(filter_with, self.get_refs(wire).items())
419 return [x[0].split('refs/heads/')[-1] for x in branches]
420
421 return _branch(context_uid, repo_id, commit_id)
422
423 @reraise_safe_exceptions
424 def commit_branches(self, wire, commit_id):
425 cache_on, context_uid, repo_id = self._cache_on(wire)
426 @self.region.conditional_cache_on_arguments(condition=cache_on)
427 def _commit_branches(_context_uid, _repo_id, _commit_id):
428 repo_init = self._factory.repo_libgit2(wire)
429 with repo_init as repo:
430 branches = [x for x in repo.branches.with_commit(_commit_id)]
431 return branches
432
433 return _commit_branches(context_uid, repo_id, commit_id)
434
435 @reraise_safe_exceptions
436 def add_object(self, wire, content):
437 repo_init = self._factory.repo_libgit2(wire)
438 with repo_init as repo:
439 blob = objects.Blob()
440 blob.set_raw_string(content)
441 repo.object_store.add_object(blob)
442 return blob.id
443
320 # TODO: this is quite complex, check if that can be simplified
444 # TODO: this is quite complex, check if that can be simplified
321 @reraise_safe_exceptions
445 @reraise_safe_exceptions
322 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
446 def commit(self, wire, commit_data, branch, commit_tree, updated, removed):
@@ -367,8 +491,7 b' class GitRemote(object):'
367 curtree = newtree
491 curtree = newtree
368 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
492 parent[reversed_dirnames[-1]] = (DIR_STAT, curtree.id)
369 else:
493 else:
370 parent.add(
494 parent.add(name=node['node_path'], mode=node['mode'], hexsha=blob.id)
371 name=node['node_path'], mode=node['mode'], hexsha=blob.id)
372
495
373 new_trees.append(parent)
496 new_trees.append(parent)
374 # Update ancestors
497 # Update ancestors
@@ -412,6 +535,9 b' class GitRemote(object):'
412 setattr(commit, k, v)
535 setattr(commit, k, v)
413 object_store.add_object(commit)
536 object_store.add_object(commit)
414
537
538 self.create_branch(wire, branch, commit.id)
539
540 # dulwich set-ref
415 ref = 'refs/heads/%s' % branch
541 ref = 'refs/heads/%s' % branch
416 repo.refs[ref] = commit.id
542 repo.refs[ref] = commit.id
417
543
@@ -454,7 +580,7 b' class GitRemote(object):'
454 # that contains a tag object, so that we would end up with
580 # that contains a tag object, so that we would end up with
455 # a peeled ref at this point.
581 # a peeled ref at this point.
456 for k in remote_refs:
582 for k in remote_refs:
457 if k.endswith(self.peeled_ref_marker):
583 if k.endswith(PEELED_REF_MARKER):
458 log.debug("Skipping peeled reference %s", k)
584 log.debug("Skipping peeled reference %s", k)
459 continue
585 continue
460 repo[k] = remote_refs[k]
586 repo[k] = remote_refs[k]
@@ -471,14 +597,19 b' class GitRemote(object):'
471 return remote_refs
597 return remote_refs
472
598
473 @reraise_safe_exceptions
599 @reraise_safe_exceptions
474 def sync_fetch(self, wire, url, refs=None):
600 def sync_fetch(self, wire, url, refs=None, all_refs=False):
475 repo = self._factory.repo(wire)
601 repo = self._factory.repo(wire)
476 if refs and not isinstance(refs, (list, tuple)):
602 if refs and not isinstance(refs, (list, tuple)):
477 refs = [refs]
603 refs = [refs]
604
478 config = self._wire_to_config(wire)
605 config = self._wire_to_config(wire)
479 # get all remote refs we'll use to fetch later
606 # get all remote refs we'll use to fetch later
607 cmd = ['ls-remote']
608 if not all_refs:
609 cmd += ['--heads', '--tags']
610 cmd += [url]
480 output, __ = self.run_git_command(
611 output, __ = self.run_git_command(
481 wire, ['ls-remote', url], fail_on_stderr=False,
612 wire, cmd, fail_on_stderr=False,
482 _copts=self._remote_conf(config),
613 _copts=self._remote_conf(config),
483 extra_env={'GIT_TERMINAL_PROMPT': '0'})
614 extra_env={'GIT_TERMINAL_PROMPT': '0'})
484
615
@@ -491,7 +622,7 b' class GitRemote(object):'
491 if ref in remote_refs:
622 if ref in remote_refs:
492 # duplicate, skip
623 # duplicate, skip
493 continue
624 continue
494 if ref.endswith(self.peeled_ref_marker):
625 if ref.endswith(PEELED_REF_MARKER):
495 log.debug("Skipping peeled reference %s", ref)
626 log.debug("Skipping peeled reference %s", ref)
496 continue
627 continue
497 # don't sync HEAD
628 # don't sync HEAD
@@ -506,6 +637,7 b' class GitRemote(object):'
506 elif not refs:
637 elif not refs:
507 fetch_refs.append('{}:{}'.format(ref, ref))
638 fetch_refs.append('{}:{}'.format(ref, ref))
508 log.debug('Finished obtaining fetch refs, total: %s', len(fetch_refs))
639 log.debug('Finished obtaining fetch refs, total: %s', len(fetch_refs))
640
509 if fetch_refs:
641 if fetch_refs:
510 for chunk in more_itertools.chunked(fetch_refs, 1024 * 4):
642 for chunk in more_itertools.chunked(fetch_refs, 1024 * 4):
511 fetch_refs_chunks = list(chunk)
643 fetch_refs_chunks = list(chunk)
@@ -523,7 +655,7 b' class GitRemote(object):'
523 if not self.check_url(url, wire):
655 if not self.check_url(url, wire):
524 return
656 return
525 config = self._wire_to_config(wire)
657 config = self._wire_to_config(wire)
526 repo = self._factory.repo(wire)
658 self._factory.repo(wire)
527 self.run_git_command(
659 self.run_git_command(
528 wire, ['push', url, '--mirror'], fail_on_stderr=False,
660 wire, ['push', url, '--mirror'], fail_on_stderr=False,
529 _copts=self._remote_conf(config),
661 _copts=self._remote_conf(config),
@@ -556,48 +688,92 b' class GitRemote(object):'
556
688
557 @reraise_safe_exceptions
689 @reraise_safe_exceptions
558 def get_object(self, wire, sha):
690 def get_object(self, wire, sha):
559 repo = self._factory.repo(wire)
691 cache_on, context_uid, repo_id = self._cache_on(wire)
560 obj = repo.get_object(sha)
692 @self.region.conditional_cache_on_arguments(condition=cache_on)
561 commit_id = obj.id
693 def _get_object(_context_uid, _repo_id, _sha):
694 repo_init = self._factory.repo_libgit2(wire)
695 with repo_init as repo:
696
697 missing_commit_err = 'Commit {} does not exist for `{}`'.format(sha, wire['path'])
698 try:
699 commit = repo.revparse_single(sha)
700 except (KeyError, ValueError) as e:
701 raise exceptions.LookupException(e)(missing_commit_err)
702
703 is_tag = False
704 if isinstance(commit, pygit2.Tag):
705 commit = repo.get(commit.target)
706 is_tag = True
562
707
563 if isinstance(obj, Tag):
708 check_dangling = True
564 commit_id = obj.object[1]
709 if is_tag:
710 check_dangling = False
711
712 # we used a reference and it parsed means we're not having a dangling commit
713 if sha != commit.hex:
714 check_dangling = False
715
716 if check_dangling:
717 # check for dangling commit
718 for branch in repo.branches.with_commit(commit.hex):
719 if branch:
720 break
721 else:
722 raise exceptions.LookupException(None)(missing_commit_err)
723
724 commit_id = commit.hex
725 type_id = commit.type
565
726
566 return {
727 return {
567 'id': obj.id,
728 'id': commit_id,
568 'type': obj.type_name,
729 'type': self._type_id_to_name(type_id),
569 'commit_id': commit_id,
730 'commit_id': commit_id,
570 'idx': 0
731 'idx': 0
571 }
732 }
572
733
573 @reraise_safe_exceptions
734 return _get_object(context_uid, repo_id, sha)
574 def get_object_attrs(self, wire, sha, *attrs):
575 repo = self._factory.repo(wire)
576 obj = repo.get_object(sha)
577 return list(getattr(obj, a) for a in attrs)
578
735
579 @reraise_safe_exceptions
736 @reraise_safe_exceptions
580 def get_refs(self, wire):
737 def get_refs(self, wire):
581 repo = self._factory.repo(wire)
738 cache_on, context_uid, repo_id = self._cache_on(wire)
582 result = {}
739 @self.region.conditional_cache_on_arguments(condition=cache_on)
583 for ref, sha in repo.refs.as_dict().items():
740 def _get_refs(_context_uid, _repo_id):
584 peeled_sha = repo.get_peeled(ref)
741
585 result[ref] = peeled_sha
742 repo_init = self._factory.repo_libgit2(wire)
586 return result
743 with repo_init as repo:
744 regex = re.compile('^refs/(heads|tags)/')
745 return {x.name: x.target.hex for x in
746 filter(lambda ref: regex.match(ref.name) ,repo.listall_reference_objects())}
747
748 return _get_refs(context_uid, repo_id)
587
749
588 @reraise_safe_exceptions
750 @reraise_safe_exceptions
589 def get_refs_path(self, wire):
751 def get_branch_pointers(self, wire):
590 repo = self._factory.repo(wire)
752 cache_on, context_uid, repo_id = self._cache_on(wire)
591 return repo.refs.path
753 @self.region.conditional_cache_on_arguments(condition=cache_on)
754 def _get_branch_pointers(_context_uid, _repo_id):
755
756 repo_init = self._factory.repo_libgit2(wire)
757 regex = re.compile('^refs/heads')
758 with repo_init as repo:
759 branches = filter(lambda ref: regex.match(ref.name), repo.listall_reference_objects())
760 return {x.target.hex: x.shorthand for x in branches}
761
762 return _get_branch_pointers(context_uid, repo_id)
592
763
593 @reraise_safe_exceptions
764 @reraise_safe_exceptions
594 def head(self, wire, show_exc=True):
765 def head(self, wire, show_exc=True):
595 repo = self._factory.repo(wire)
766 cache_on, context_uid, repo_id = self._cache_on(wire)
767 @self.region.conditional_cache_on_arguments(condition=cache_on)
768 def _head(_context_uid, _repo_id, _show_exc):
769 repo_init = self._factory.repo_libgit2(wire)
770 with repo_init as repo:
596 try:
771 try:
597 return repo.head()
772 return repo.head.peel().hex
598 except Exception:
773 except Exception:
599 if show_exc:
774 if show_exc:
600 raise
775 raise
776 return _head(context_uid, repo_id, show_exc)
601
777
602 @reraise_safe_exceptions
778 @reraise_safe_exceptions
603 def init(self, wire):
779 def init(self, wire):
@@ -611,35 +787,141 b' class GitRemote(object):'
611
787
612 @reraise_safe_exceptions
788 @reraise_safe_exceptions
613 def revision(self, wire, rev):
789 def revision(self, wire, rev):
614 repo = self._factory.repo(wire)
790
615 obj = repo[rev]
791 cache_on, context_uid, repo_id = self._cache_on(wire)
792 @self.region.conditional_cache_on_arguments(condition=cache_on)
793 def _revision(_context_uid, _repo_id, _rev):
794 repo_init = self._factory.repo_libgit2(wire)
795 with repo_init as repo:
796 commit = repo[rev]
616 obj_data = {
797 obj_data = {
617 'id': obj.id,
798 'id': commit.id.hex,
618 }
799 }
619 try:
800 # tree objects itself don't have tree_id attribute
620 obj_data['tree'] = obj.tree
801 if hasattr(commit, 'tree_id'):
621 except AttributeError:
802 obj_data['tree'] = commit.tree_id.hex
622 pass
803
623 return obj_data
804 return obj_data
805 return _revision(context_uid, repo_id, rev)
806
807 @reraise_safe_exceptions
808 def date(self, wire, commit_id):
809 cache_on, context_uid, repo_id = self._cache_on(wire)
810 @self.region.conditional_cache_on_arguments(condition=cache_on)
811 def _date(_repo_id, _commit_id):
812 repo_init = self._factory.repo_libgit2(wire)
813 with repo_init as repo:
814 commit = repo[commit_id]
815
816 if hasattr(commit, 'commit_time'):
817 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
818 else:
819 commit = commit.get_object()
820 commit_time, commit_time_offset = commit.commit_time, commit.commit_time_offset
821
822 # TODO(marcink): check dulwich difference of offset vs timezone
823 return [commit_time, commit_time_offset]
824 return _date(repo_id, commit_id)
624
825
625 @reraise_safe_exceptions
826 @reraise_safe_exceptions
626 def commit_attribute(self, wire, rev, attr):
827 def author(self, wire, commit_id):
627 repo = self._factory.repo(wire)
828 cache_on, context_uid, repo_id = self._cache_on(wire)
628 obj = repo[rev]
829 @self.region.conditional_cache_on_arguments(condition=cache_on)
629 return getattr(obj, attr)
830 def _author(_repo_id, _commit_id):
831 repo_init = self._factory.repo_libgit2(wire)
832 with repo_init as repo:
833 commit = repo[commit_id]
834
835 if hasattr(commit, 'author'):
836 author = commit.author
837 else:
838 author = commit.get_object().author
839
840 if author.email:
841 return u"{} <{}>".format(author.name, author.email)
842
843 return u"{}".format(author.raw_name)
844 return _author(repo_id, commit_id)
845
846 @reraise_safe_exceptions
847 def message(self, wire, commit_id):
848 cache_on, context_uid, repo_id = self._cache_on(wire)
849 @self.region.conditional_cache_on_arguments(condition=cache_on)
850 def _message(_repo_id, _commit_id):
851 repo_init = self._factory.repo_libgit2(wire)
852 with repo_init as repo:
853 commit = repo[commit_id]
854 return commit.message
855 return _message(repo_id, commit_id)
856
857 @reraise_safe_exceptions
858 def parents(self, wire, commit_id):
859 cache_on, context_uid, repo_id = self._cache_on(wire)
860 @self.region.conditional_cache_on_arguments(condition=cache_on)
861 def _parents(_repo_id, _commit_id):
862 repo_init = self._factory.repo_libgit2(wire)
863 with repo_init as repo:
864 commit = repo[commit_id]
865 if hasattr(commit, 'parent_ids'):
866 parent_ids = commit.parent_ids
867 else:
868 parent_ids = commit.get_object().parent_ids
869
870 return [x.hex for x in parent_ids]
871 return _parents(repo_id, commit_id)
872
873 @reraise_safe_exceptions
874 def children(self, wire, commit_id):
875 cache_on, context_uid, repo_id = self._cache_on(wire)
876 @self.region.conditional_cache_on_arguments(condition=cache_on)
877 def _children(_repo_id, _commit_id):
878 output, __ = self.run_git_command(
879 wire, ['rev-list', '--all', '--children'])
880
881 child_ids = []
882 pat = re.compile(r'^%s' % commit_id)
883 for l in output.splitlines():
884 if pat.match(l):
885 found_ids = l.split(' ')[1:]
886 child_ids.extend(found_ids)
887
888 return child_ids
889 return _children(repo_id, commit_id)
630
890
631 @reraise_safe_exceptions
891 @reraise_safe_exceptions
632 def set_refs(self, wire, key, value):
892 def set_refs(self, wire, key, value):
633 repo = self._factory.repo(wire)
893 repo_init = self._factory.repo_libgit2(wire)
634 repo.refs[key] = value
894 with repo_init as repo:
895 repo.references.create(key, value, force=True)
896
897 @reraise_safe_exceptions
898 def create_branch(self, wire, branch_name, commit_id, force=False):
899 repo_init = self._factory.repo_libgit2(wire)
900 with repo_init as repo:
901 commit = repo[commit_id]
902
903 if force:
904 repo.branches.local.create(branch_name, commit, force=force)
905 elif not repo.branches.get(branch_name):
906 # create only if that branch isn't existing
907 repo.branches.local.create(branch_name, commit, force=force)
635
908
636 @reraise_safe_exceptions
909 @reraise_safe_exceptions
637 def remove_ref(self, wire, key):
910 def remove_ref(self, wire, key):
638 repo = self._factory.repo(wire)
911 repo_init = self._factory.repo_libgit2(wire)
639 del repo.refs[key]
912 with repo_init as repo:
913 repo.references.delete(key)
914
915 @reraise_safe_exceptions
916 def tag_remove(self, wire, tag_name):
917 repo_init = self._factory.repo_libgit2(wire)
918 with repo_init as repo:
919 key = 'refs/tags/{}'.format(tag_name)
920 repo.references.delete(key)
640
921
641 @reraise_safe_exceptions
922 @reraise_safe_exceptions
642 def tree_changes(self, wire, source_id, target_id):
923 def tree_changes(self, wire, source_id, target_id):
924 # TODO(marcink): remove this seems it's only used by tests
643 repo = self._factory.repo(wire)
925 repo = self._factory.repo(wire)
644 source = repo[source_id].tree if source_id else None
926 source = repo[source_id].tree if source_id else None
645 target = repo[target_id].tree
927 target = repo[target_id].tree
@@ -647,21 +929,158 b' class GitRemote(object):'
647 return list(result)
929 return list(result)
648
930
649 @reraise_safe_exceptions
931 @reraise_safe_exceptions
932 def tree_and_type_for_path(self, wire, commit_id, path):
933
934 cache_on, context_uid, repo_id = self._cache_on(wire)
935 @self.region.conditional_cache_on_arguments(condition=cache_on)
936 def _tree_and_type_for_path(_context_uid, _repo_id, _commit_id, _path):
937 repo_init = self._factory.repo_libgit2(wire)
938
939 with repo_init as repo:
940 commit = repo[commit_id]
941 try:
942 tree = commit.tree[path]
943 except KeyError:
944 return None, None, None
945
946 return tree.id.hex, tree.type, tree.filemode
947 return _tree_and_type_for_path(context_uid, repo_id, commit_id, path)
948
949 @reraise_safe_exceptions
650 def tree_items(self, wire, tree_id):
950 def tree_items(self, wire, tree_id):
651 repo = self._factory.repo(wire)
951 cache_on, context_uid, repo_id = self._cache_on(wire)
952 @self.region.conditional_cache_on_arguments(condition=cache_on)
953 def _tree_items(_repo_id, _tree_id):
954
955 repo_init = self._factory.repo_libgit2(wire)
956 with repo_init as repo:
957 try:
652 tree = repo[tree_id]
958 tree = repo[tree_id]
959 except KeyError:
960 raise ObjectMissing('No tree with id: {}'.format(tree_id))
653
961
654 result = []
962 result = []
655 for item in tree.iteritems():
963 for item in tree:
656 item_sha = item.sha
964 item_sha = item.hex
657 item_mode = item.mode
965 item_mode = item.filemode
966 item_type = item.type
967
968 if item_type == 'commit':
969 # NOTE(marcink): submodules we translate to 'link' for backward compat
970 item_type = 'link'
971
972 result.append((item.name, item_mode, item_sha, item_type))
973 return result
974 return _tree_items(repo_id, tree_id)
975
976 @reraise_safe_exceptions
977 def diff_2(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
978 """
979 Old version that uses subprocess to call diff
980 """
981
982 flags = [
983 '-U%s' % context, '--patch',
984 '--binary',
985 '--find-renames',
986 '--no-indent-heuristic',
987 # '--indent-heuristic',
988 #'--full-index',
989 #'--abbrev=40'
990 ]
991
992 if opt_ignorews:
993 flags.append('--ignore-all-space')
994
995 if commit_id_1 == self.EMPTY_COMMIT:
996 cmd = ['show'] + flags + [commit_id_2]
997 else:
998 cmd = ['diff'] + flags + [commit_id_1, commit_id_2]
999
1000 if file_filter:
1001 cmd.extend(['--', file_filter])
1002
1003 diff, __ = self.run_git_command(wire, cmd)
1004 # If we used 'show' command, strip first few lines (until actual diff
1005 # starts)
1006 if commit_id_1 == self.EMPTY_COMMIT:
1007 lines = diff.splitlines()
1008 x = 0
1009 for line in lines:
1010 if line.startswith('diff'):
1011 break
1012 x += 1
1013 # Append new line just like 'diff' command do
1014 diff = '\n'.join(lines[x:]) + '\n'
1015 return diff
658
1016
659 if FILE_MODE(item_mode) == GIT_LINK:
1017 @reraise_safe_exceptions
660 item_type = "link"
1018 def diff(self, wire, commit_id_1, commit_id_2, file_filter, opt_ignorews, context):
1019 repo_init = self._factory.repo_libgit2(wire)
1020 with repo_init as repo:
1021 swap = True
1022 flags = 0
1023 flags |= pygit2.GIT_DIFF_SHOW_BINARY
1024
1025 if opt_ignorews:
1026 flags |= pygit2.GIT_DIFF_IGNORE_WHITESPACE
1027
1028 if commit_id_1 == self.EMPTY_COMMIT:
1029 comm1 = repo[commit_id_2]
1030 diff_obj = comm1.tree.diff_to_tree(
1031 flags=flags, context_lines=context, swap=swap)
1032
661 else:
1033 else:
662 item_type = repo[item_sha].type_name
1034 comm1 = repo[commit_id_2]
1035 comm2 = repo[commit_id_1]
1036 diff_obj = comm1.tree.diff_to_tree(
1037 comm2.tree, flags=flags, context_lines=context, swap=swap)
1038 similar_flags = 0
1039 similar_flags |= pygit2.GIT_DIFF_FIND_RENAMES
1040 diff_obj.find_similar(flags=similar_flags)
1041
1042 if file_filter:
1043 for p in diff_obj:
1044 if p.delta.old_file.path == file_filter:
1045 return p.patch or ''
1046 # fo matching path == no diff
1047 return ''
1048 return diff_obj.patch or ''
663
1049
664 result.append((item.path, item_mode, item_sha, item_type))
1050 @reraise_safe_exceptions
1051 def node_history(self, wire, commit_id, path, limit):
1052 cache_on, context_uid, repo_id = self._cache_on(wire)
1053 @self.region.conditional_cache_on_arguments(condition=cache_on)
1054 def _node_history(_context_uid, _repo_id, _commit_id, _path, _limit):
1055 # optimize for n==1, rev-list is much faster for that use-case
1056 if limit == 1:
1057 cmd = ['rev-list', '-1', commit_id, '--', path]
1058 else:
1059 cmd = ['log']
1060 if limit:
1061 cmd.extend(['-n', str(safe_int(limit, 0))])
1062 cmd.extend(['--pretty=format: %H', '-s', commit_id, '--', path])
1063
1064 output, __ = self.run_git_command(wire, cmd)
1065 commit_ids = re.findall(r'[0-9a-fA-F]{40}', output)
1066
1067 return [x for x in commit_ids]
1068 return _node_history(context_uid, repo_id, commit_id, path, limit)
1069
1070 @reraise_safe_exceptions
1071 def node_annotate(self, wire, commit_id, path):
1072
1073 cmd = ['blame', '-l', '--root', '-r', commit_id, '--', path]
1074 # -l ==> outputs long shas (and we need all 40 characters)
1075 # --root ==> doesn't put '^' character for boundaries
1076 # -r commit_id ==> blames for the given commit
1077 output, __ = self.run_git_command(wire, cmd)
1078
1079 result = []
1080 for i, blame_line in enumerate(output.split('\n')[:-1]):
1081 line_no = i + 1
1082 commit_id, line = re.split(r' ', blame_line, 1)
1083 result.append((line_no, commit_id, line))
665 return result
1084 return result
666
1085
667 @reraise_safe_exceptions
1086 @reraise_safe_exceptions
@@ -670,13 +1089,20 b' class GitRemote(object):'
670 update_server_info(repo)
1089 update_server_info(repo)
671
1090
672 @reraise_safe_exceptions
1091 @reraise_safe_exceptions
673 def discover_git_version(self):
1092 def get_all_commit_ids(self, wire):
674 stdout, _ = self.run_git_command(
1093
675 {}, ['--version'], _bare=True, _safe=True)
1094 cache_on, context_uid, repo_id = self._cache_on(wire)
676 prefix = 'git version'
1095 @self.region.conditional_cache_on_arguments(condition=cache_on)
677 if stdout.startswith(prefix):
1096 def _get_all_commit_ids(_context_uid, _repo_id):
678 stdout = stdout[len(prefix):]
1097
679 return stdout.strip()
1098 cmd = ['rev-list', '--reverse', '--date-order', '--branches', '--tags']
1099 try:
1100 output, __ = self.run_git_command(wire, cmd)
1101 return output.splitlines()
1102 except Exception:
1103 # Can be raised for empty repositories
1104 return []
1105 return _get_all_commit_ids(context_uid, repo_id)
680
1106
681 @reraise_safe_exceptions
1107 @reraise_safe_exceptions
682 def run_git_command(self, wire, cmd, **opts):
1108 def run_git_command(self, wire, cmd, **opts):
@@ -711,11 +1137,12 b' class GitRemote(object):'
711 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
1137 cmd = [settings.GIT_EXECUTABLE] + _copts + cmd
712 _opts = {'env': gitenv, 'shell': False}
1138 _opts = {'env': gitenv, 'shell': False}
713
1139
1140 proc = None
714 try:
1141 try:
715 _opts.update(opts)
1142 _opts.update(opts)
716 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
1143 proc = subprocessio.SubprocessIOChunker(cmd, **_opts)
717
1144
718 return ''.join(p), ''.join(p.error)
1145 return ''.join(proc), ''.join(proc.error)
719 except (EnvironmentError, OSError) as err:
1146 except (EnvironmentError, OSError) as err:
720 cmd = ' '.join(cmd) # human friendly CMD
1147 cmd = ' '.join(cmd) # human friendly CMD
721 tb_err = ("Couldn't run git command (%s).\n"
1148 tb_err = ("Couldn't run git command (%s).\n"
@@ -727,26 +1154,24 b' class GitRemote(object):'
727 return '', err
1154 return '', err
728 else:
1155 else:
729 raise exceptions.VcsException()(tb_err)
1156 raise exceptions.VcsException()(tb_err)
1157 finally:
1158 if proc:
1159 proc.close()
730
1160
731 @reraise_safe_exceptions
1161 @reraise_safe_exceptions
732 def install_hooks(self, wire, force=False):
1162 def install_hooks(self, wire, force=False):
733 from vcsserver.hook_utils import install_git_hooks
1163 from vcsserver.hook_utils import install_git_hooks
734 repo = self._factory.repo(wire)
1164 bare = self.bare(wire)
735 return install_git_hooks(repo.path, repo.bare, force_create=force)
1165 path = wire['path']
1166 return install_git_hooks(path, bare, force_create=force)
736
1167
737 @reraise_safe_exceptions
1168 @reraise_safe_exceptions
738 def get_hooks_info(self, wire):
1169 def get_hooks_info(self, wire):
739 from vcsserver.hook_utils import (
1170 from vcsserver.hook_utils import (
740 get_git_pre_hook_version, get_git_post_hook_version)
1171 get_git_pre_hook_version, get_git_post_hook_version)
741 repo = self._factory.repo(wire)
1172 bare = self.bare(wire)
1173 path = wire['path']
742 return {
1174 return {
743 'pre_version': get_git_pre_hook_version(repo.path, repo.bare),
1175 'pre_version': get_git_pre_hook_version(path, bare),
744 'post_version': get_git_post_hook_version(repo.path, repo.bare),
1176 'post_version': get_git_post_hook_version(path, bare),
745 }
1177 }
746
747
748 def str_to_dulwich(value):
749 """
750 Dulwich 0.10.1a requires `unicode` objects to be passed in.
751 """
752 return value.decode(settings.WIRE_ENCODING)
@@ -22,7 +22,7 b' import urllib'
22 import urllib2
22 import urllib2
23 import traceback
23 import traceback
24
24
25 from hgext import largefiles, rebase
25 from hgext import largefiles, rebase, purge
26 from hgext.strip import strip as hgext_strip
26 from hgext.strip import strip as hgext_strip
27 from mercurial import commands
27 from mercurial import commands
28 from mercurial import unionrepo
28 from mercurial import unionrepo
@@ -37,6 +37,7 b' from vcsserver.hgcompat import ('
37 makepeer, instance, match, memctx, exchange, memfilectx, nullrev, hg_merge,
37 makepeer, instance, match, memctx, exchange, memfilectx, nullrev, hg_merge,
38 patch, peer, revrange, ui, hg_tag, Abort, LookupError, RepoError,
38 patch, peer, revrange, ui, hg_tag, Abort, LookupError, RepoError,
39 RepoLookupError, InterventionRequired, RequirementError)
39 RepoLookupError, InterventionRequired, RequirementError)
40 from vcsserver.vcs_base import RemoteBase
40
41
41 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
42
43
@@ -98,6 +99,7 b' def make_ui_from_config(repo_config):'
98
99
99 def reraise_safe_exceptions(func):
100 def reraise_safe_exceptions(func):
100 """Decorator for converting mercurial exceptions to something neutral."""
101 """Decorator for converting mercurial exceptions to something neutral."""
102
101 def wrapper(*args, **kwargs):
103 def wrapper(*args, **kwargs):
102 try:
104 try:
103 return func(*args, **kwargs)
105 return func(*args, **kwargs)
@@ -142,12 +144,17 b' class MercurialFactory(RepoFactory):'
142 baseui = self._create_config(wire["config"])
144 baseui = self._create_config(wire["config"])
143 return instance(baseui, wire["path"], create)
145 return instance(baseui, wire["path"], create)
144
146
147 def repo(self, wire, create=False):
148 """
149 Get a repository instance for the given path.
150 """
151 return self._create_repo(wire, create)
145
152
146 class HgRemote(object):
153
154 class HgRemote(RemoteBase):
147
155
148 def __init__(self, factory):
156 def __init__(self, factory):
149 self._factory = factory
157 self._factory = factory
150
151 self._bulk_methods = {
158 self._bulk_methods = {
152 "affected_files": self.ctx_files,
159 "affected_files": self.ctx_files,
153 "author": self.ctx_user,
160 "author": self.ctx_user,
@@ -199,11 +206,19 b' class HgRemote(object):'
199
206
200 @reraise_safe_exceptions
207 @reraise_safe_exceptions
201 def bookmarks(self, wire):
208 def bookmarks(self, wire):
209 cache_on, context_uid, repo_id = self._cache_on(wire)
210 @self.region.conditional_cache_on_arguments(condition=cache_on)
211 def _bookmarks(_context_uid, _repo_id):
202 repo = self._factory.repo(wire)
212 repo = self._factory.repo(wire)
203 return dict(repo._bookmarks)
213 return dict(repo._bookmarks)
204
214
215 return _bookmarks(context_uid, repo_id)
216
205 @reraise_safe_exceptions
217 @reraise_safe_exceptions
206 def branches(self, wire, normal, closed):
218 def branches(self, wire, normal, closed):
219 cache_on, context_uid, repo_id = self._cache_on(wire)
220 @self.region.conditional_cache_on_arguments(condition=cache_on)
221 def _branches(_context_uid, _repo_id, _normal, _closed):
207 repo = self._factory.repo(wire)
222 repo = self._factory.repo(wire)
208 iter_branches = repo.branchmap().iterbranches()
223 iter_branches = repo.branchmap().iterbranches()
209 bt = {}
224 bt = {}
@@ -215,97 +230,44 b' class HgRemote(object):'
215
230
216 return bt
231 return bt
217
232
233 return _branches(context_uid, repo_id, normal, closed)
234
218 @reraise_safe_exceptions
235 @reraise_safe_exceptions
219 def bulk_request(self, wire, rev, pre_load):
236 def bulk_request(self, wire, commit_id, pre_load):
237 cache_on, context_uid, repo_id = self._cache_on(wire)
238 @self.region.conditional_cache_on_arguments(condition=cache_on)
239 def _bulk_request(_repo_id, _commit_id, _pre_load):
220 result = {}
240 result = {}
221 for attr in pre_load:
241 for attr in pre_load:
222 try:
242 try:
223 method = self._bulk_methods[attr]
243 method = self._bulk_methods[attr]
224 result[attr] = method(wire, rev)
244 result[attr] = method(wire, commit_id)
225 except KeyError as e:
245 except KeyError as e:
226 raise exceptions.VcsException(e)(
246 raise exceptions.VcsException(e)(
227 'Unknown bulk attribute: "%s"' % attr)
247 'Unknown bulk attribute: "%s"' % attr)
228 return result
248 return result
229
249
230 @reraise_safe_exceptions
250 return _bulk_request(repo_id, commit_id, sorted(pre_load))
231 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
232 baseui = self._factory._create_config(wire["config"], hooks=hooks)
233 clone(baseui, source, dest, noupdate=not update_after_clone)
234
251
235 @reraise_safe_exceptions
252 @reraise_safe_exceptions
236 def commitctx(
253 def ctx_branch(self, wire, commit_id):
237 self, wire, message, parents, commit_time, commit_timezone,
254 cache_on, context_uid, repo_id = self._cache_on(wire)
238 user, files, extra, removed, updated):
255 @self.region.conditional_cache_on_arguments(condition=cache_on)
239
256 def _ctx_branch(_repo_id, _commit_id):
240 repo = self._factory.repo(wire)
257 repo = self._factory.repo(wire)
241 baseui = self._factory._create_config(wire['config'])
258 ctx = self._get_ctx(repo, commit_id)
242 publishing = baseui.configbool('phases', 'publish')
259 return ctx.branch()
243 if publishing:
260 return _ctx_branch(repo_id, commit_id)
244 new_commit = 'public'
245 else:
246 new_commit = 'draft'
247
248 def _filectxfn(_repo, ctx, path):
249 """
250 Marks given path as added/changed/removed in a given _repo. This is
251 for internal mercurial commit function.
252 """
253
254 # check if this path is removed
255 if path in removed:
256 # returning None is a way to mark node for removal
257 return None
258
259 # check if this path is added
260 for node in updated:
261 if node['path'] == path:
262 return memfilectx(
263 _repo,
264 changectx=ctx,
265 path=node['path'],
266 data=node['content'],
267 islink=False,
268 isexec=bool(node['mode'] & stat.S_IXUSR),
269 copied=False)
270
271 raise exceptions.AbortException()(
272 "Given path haven't been marked as added, "
273 "changed or removed (%s)" % path)
274
275 with repo.ui.configoverride({('phases', 'new-commit'): new_commit}):
276
277 commit_ctx = memctx(
278 repo=repo,
279 parents=parents,
280 text=message,
281 files=files,
282 filectxfn=_filectxfn,
283 user=user,
284 date=(commit_time, commit_timezone),
285 extra=extra)
286
287 n = repo.commitctx(commit_ctx)
288 new_id = hex(n)
289
290 return new_id
291
261
292 @reraise_safe_exceptions
262 @reraise_safe_exceptions
293 def ctx_branch(self, wire, revision):
263 def ctx_date(self, wire, commit_id):
294 repo = self._factory.repo(wire)
264 cache_on, context_uid, repo_id = self._cache_on(wire)
295 ctx = self._get_ctx(repo, revision)
265 @self.region.conditional_cache_on_arguments(condition=cache_on)
296 return ctx.branch()
266 def _ctx_date(_repo_id, _commit_id):
297
298 @reraise_safe_exceptions
299 def ctx_children(self, wire, revision):
300 repo = self._factory.repo(wire)
267 repo = self._factory.repo(wire)
301 ctx = self._get_ctx(repo, revision)
268 ctx = self._get_ctx(repo, commit_id)
302 return [child.rev() for child in ctx.children()]
303
304 @reraise_safe_exceptions
305 def ctx_date(self, wire, revision):
306 repo = self._factory.repo(wire)
307 ctx = self._get_ctx(repo, revision)
308 return ctx.date()
269 return ctx.date()
270 return _ctx_date(repo_id, commit_id)
309
271
310 @reraise_safe_exceptions
272 @reraise_safe_exceptions
311 def ctx_description(self, wire, revision):
273 def ctx_description(self, wire, revision):
@@ -314,11 +276,16 b' class HgRemote(object):'
314 return ctx.description()
276 return ctx.description()
315
277
316 @reraise_safe_exceptions
278 @reraise_safe_exceptions
317 def ctx_files(self, wire, revision):
279 def ctx_files(self, wire, commit_id):
280 cache_on, context_uid, repo_id = self._cache_on(wire)
281 @self.region.conditional_cache_on_arguments(condition=cache_on)
282 def _ctx_files(_repo_id, _commit_id):
318 repo = self._factory.repo(wire)
283 repo = self._factory.repo(wire)
319 ctx = self._get_ctx(repo, revision)
284 ctx = self._get_ctx(repo, commit_id)
320 return ctx.files()
285 return ctx.files()
321
286
287 return _ctx_files(repo_id, commit_id)
288
322 @reraise_safe_exceptions
289 @reraise_safe_exceptions
323 def ctx_list(self, path, revision):
290 def ctx_list(self, path, revision):
324 repo = self._factory.repo(path)
291 repo = self._factory.repo(path)
@@ -326,29 +293,59 b' class HgRemote(object):'
326 return list(ctx)
293 return list(ctx)
327
294
328 @reraise_safe_exceptions
295 @reraise_safe_exceptions
329 def ctx_parents(self, wire, revision):
296 def ctx_parents(self, wire, commit_id):
297 cache_on, context_uid, repo_id = self._cache_on(wire)
298 @self.region.conditional_cache_on_arguments(condition=cache_on)
299 def _ctx_parents(_repo_id, _commit_id):
330 repo = self._factory.repo(wire)
300 repo = self._factory.repo(wire)
331 ctx = self._get_ctx(repo, revision)
301 ctx = self._get_ctx(repo, commit_id)
332 return [parent.rev() for parent in ctx.parents()]
302 return [parent.hex() for parent in ctx.parents()
303 if not (parent.hidden() or parent.obsolete())]
304
305 return _ctx_parents(repo_id, commit_id)
333
306
334 @reraise_safe_exceptions
307 @reraise_safe_exceptions
335 def ctx_phase(self, wire, revision):
308 def ctx_children(self, wire, commit_id):
309 cache_on, context_uid, repo_id = self._cache_on(wire)
310 @self.region.conditional_cache_on_arguments(condition=cache_on)
311 def _ctx_children(_repo_id, _commit_id):
336 repo = self._factory.repo(wire)
312 repo = self._factory.repo(wire)
337 ctx = self._get_ctx(repo, revision)
313 ctx = self._get_ctx(repo, commit_id)
314 return [child.hex() for child in ctx.children()
315 if not (child.hidden() or child.obsolete())]
316
317 return _ctx_children(repo_id, commit_id)
318
319 @reraise_safe_exceptions
320 def ctx_phase(self, wire, commit_id):
321 cache_on, context_uid, repo_id = self._cache_on(wire)
322 @self.region.conditional_cache_on_arguments(condition=cache_on)
323 def _ctx_phase(_context_uid, _repo_id, _commit_id):
324 repo = self._factory.repo(wire)
325 ctx = self._get_ctx(repo, commit_id)
338 # public=0, draft=1, secret=3
326 # public=0, draft=1, secret=3
339 return ctx.phase()
327 return ctx.phase()
328 return _ctx_phase(context_uid, repo_id, commit_id)
340
329
341 @reraise_safe_exceptions
330 @reraise_safe_exceptions
342 def ctx_obsolete(self, wire, revision):
331 def ctx_obsolete(self, wire, commit_id):
332 cache_on, context_uid, repo_id = self._cache_on(wire)
333 @self.region.conditional_cache_on_arguments(condition=cache_on)
334 def _ctx_obsolete(_context_uid, _repo_id, _commit_id):
343 repo = self._factory.repo(wire)
335 repo = self._factory.repo(wire)
344 ctx = self._get_ctx(repo, revision)
336 ctx = self._get_ctx(repo, commit_id)
345 return ctx.obsolete()
337 return ctx.obsolete()
338 return _ctx_obsolete(context_uid, repo_id, commit_id)
346
339
347 @reraise_safe_exceptions
340 @reraise_safe_exceptions
348 def ctx_hidden(self, wire, revision):
341 def ctx_hidden(self, wire, commit_id):
342 cache_on, context_uid, repo_id = self._cache_on(wire)
343 @self.region.conditional_cache_on_arguments(condition=cache_on)
344 def _ctx_hidden(_context_uid, _repo_id, _commit_id):
349 repo = self._factory.repo(wire)
345 repo = self._factory.repo(wire)
350 ctx = self._get_ctx(repo, revision)
346 ctx = self._get_ctx(repo, commit_id)
351 return ctx.hidden()
347 return ctx.hidden()
348 return _ctx_hidden(context_uid, repo_id, commit_id)
352
349
353 @reraise_safe_exceptions
350 @reraise_safe_exceptions
354 def ctx_substate(self, wire, revision):
351 def ctx_substate(self, wire, revision):
@@ -438,9 +435,7 b' class HgRemote(object):'
438 return True
435 return True
439
436
440 @reraise_safe_exceptions
437 @reraise_safe_exceptions
441 def diff(
438 def diff(self, wire, commit_id_1, commit_id_2, file_filter, opt_git, opt_ignorews, context):
442 self, wire, rev1, rev2, file_filter, opt_git, opt_ignorews,
443 context):
444 repo = self._factory.repo(wire)
439 repo = self._factory.repo(wire)
445
440
446 if file_filter:
441 if file_filter:
@@ -451,12 +446,15 b' class HgRemote(object):'
451
446
452 try:
447 try:
453 return "".join(patch.diff(
448 return "".join(patch.diff(
454 repo, node1=rev1, node2=rev2, match=match_filter, opts=opts))
449 repo, node1=commit_id_1, node2=commit_id_2, match=match_filter, opts=opts))
455 except RepoLookupError as e:
450 except RepoLookupError as e:
456 raise exceptions.LookupException(e)()
451 raise exceptions.LookupException(e)()
457
452
458 @reraise_safe_exceptions
453 @reraise_safe_exceptions
459 def node_history(self, wire, revision, path, limit):
454 def node_history(self, wire, revision, path, limit):
455 cache_on, context_uid, repo_id = self._cache_on(wire)
456 @self.region.conditional_cache_on_arguments(condition=cache_on)
457 def _node_history(_context_uid, _repo_id, _revision, _path, _limit):
460 repo = self._factory.repo(wire)
458 repo = self._factory.repo(wire)
461
459
462 ctx = self._get_ctx(repo, revision)
460 ctx = self._get_ctx(repo, revision)
@@ -480,9 +478,13 b' class HgRemote(object):'
480 history.append(hex(obj.node()))
478 history.append(hex(obj.node()))
481
479
482 return [x for x in history]
480 return [x for x in history]
481 return _node_history(context_uid, repo_id, revision, path, limit)
483
482
484 @reraise_safe_exceptions
483 @reraise_safe_exceptions
485 def node_history_untill(self, wire, revision, path, limit):
484 def node_history_untill(self, wire, revision, path, limit):
485 cache_on, context_uid, repo_id = self._cache_on(wire)
486 @self.region.conditional_cache_on_arguments(condition=cache_on)
487 def _node_history_until(_context_uid, _repo_id):
486 repo = self._factory.repo(wire)
488 repo = self._factory.repo(wire)
487 ctx = self._get_ctx(repo, revision)
489 ctx = self._get_ctx(repo, revision)
488 fctx = ctx.filectx(path)
490 fctx = ctx.filectx(path)
@@ -493,6 +495,7 b' class HgRemote(object):'
493 file_log = file_log[-limit:]
495 file_log = file_log[-limit:]
494
496
495 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
497 return [hex(fctx.filectx(cs).node()) for cs in reversed(file_log)]
498 return _node_history_until(context_uid, repo_id, revision, path, limit)
496
499
497 @reraise_safe_exceptions
500 @reraise_safe_exceptions
498 def fctx_annotate(self, wire, revision, path):
501 def fctx_annotate(self, wire, revision, path):
@@ -509,32 +512,45 b' class HgRemote(object):'
509 return result
512 return result
510
513
511 @reraise_safe_exceptions
514 @reraise_safe_exceptions
512 def fctx_data(self, wire, revision, path):
515 def fctx_node_data(self, wire, revision, path):
513 repo = self._factory.repo(wire)
516 repo = self._factory.repo(wire)
514 ctx = self._get_ctx(repo, revision)
517 ctx = self._get_ctx(repo, revision)
515 fctx = ctx.filectx(path)
518 fctx = ctx.filectx(path)
516 return fctx.data()
519 return fctx.data()
517
520
518 @reraise_safe_exceptions
521 @reraise_safe_exceptions
519 def fctx_flags(self, wire, revision, path):
522 def fctx_flags(self, wire, commit_id, path):
523 cache_on, context_uid, repo_id = self._cache_on(wire)
524 @self.region.conditional_cache_on_arguments(condition=cache_on)
525 def _fctx_flags(_repo_id, _commit_id, _path):
520 repo = self._factory.repo(wire)
526 repo = self._factory.repo(wire)
521 ctx = self._get_ctx(repo, revision)
527 ctx = self._get_ctx(repo, commit_id)
522 fctx = ctx.filectx(path)
528 fctx = ctx.filectx(path)
523 return fctx.flags()
529 return fctx.flags()
524
530
531 return _fctx_flags(repo_id, commit_id, path)
532
525 @reraise_safe_exceptions
533 @reraise_safe_exceptions
526 def fctx_size(self, wire, revision, path):
534 def fctx_size(self, wire, commit_id, path):
535 cache_on, context_uid, repo_id = self._cache_on(wire)
536 @self.region.conditional_cache_on_arguments(condition=cache_on)
537 def _fctx_size(_repo_id, _revision, _path):
527 repo = self._factory.repo(wire)
538 repo = self._factory.repo(wire)
528 ctx = self._get_ctx(repo, revision)
539 ctx = self._get_ctx(repo, commit_id)
529 fctx = ctx.filectx(path)
540 fctx = ctx.filectx(path)
530 return fctx.size()
541 return fctx.size()
542 return _fctx_size(repo_id, commit_id, path)
531
543
532 @reraise_safe_exceptions
544 @reraise_safe_exceptions
533 def get_all_commit_ids(self, wire, name):
545 def get_all_commit_ids(self, wire, name):
546 cache_on, context_uid, repo_id = self._cache_on(wire)
547 @self.region.conditional_cache_on_arguments(condition=cache_on)
548 def _get_all_commit_ids(_context_uid, _repo_id, _name):
534 repo = self._factory.repo(wire)
549 repo = self._factory.repo(wire)
535 repo = repo.filtered(name)
550 repo = repo.filtered(name)
536 revs = map(lambda x: hex(x[7]), repo.changelog.index)
551 revs = map(lambda x: hex(x[7]), repo.changelog.index)
537 return revs
552 return revs
553 return _get_all_commit_ids(context_uid, repo_id, name)
538
554
539 @reraise_safe_exceptions
555 @reraise_safe_exceptions
540 def get_config_value(self, wire, section, name, untrusted=False):
556 def get_config_value(self, wire, section, name, untrusted=False):
@@ -542,18 +558,26 b' class HgRemote(object):'
542 return repo.ui.config(section, name, untrusted=untrusted)
558 return repo.ui.config(section, name, untrusted=untrusted)
543
559
544 @reraise_safe_exceptions
560 @reraise_safe_exceptions
545 def get_config_bool(self, wire, section, name, untrusted=False):
561 def is_large_file(self, wire, commit_id, path):
546 repo = self._factory.repo(wire)
562 cache_on, context_uid, repo_id = self._cache_on(wire)
547 return repo.ui.configbool(section, name, untrusted=untrusted)
563 @self.region.conditional_cache_on_arguments(condition=cache_on)
564 def _is_large_file(_context_uid, _repo_id, _commit_id, _path):
565 return largefiles.lfutil.isstandin(path)
566
567 return _is_large_file(context_uid, repo_id, commit_id, path)
548
568
549 @reraise_safe_exceptions
569 @reraise_safe_exceptions
550 def get_config_list(self, wire, section, name, untrusted=False):
570 def is_binary(self, wire, revision, path):
551 repo = self._factory.repo(wire)
571 cache_on, context_uid, repo_id = self._cache_on(wire)
552 return repo.ui.configlist(section, name, untrusted=untrusted)
553
572
554 @reraise_safe_exceptions
573 @self.region.conditional_cache_on_arguments(condition=cache_on)
555 def is_large_file(self, wire, path):
574 def _is_binary(_repo_id, _sha, _path):
556 return largefiles.lfutil.isstandin(path)
575 repo = self._factory.repo(wire)
576 ctx = self._get_ctx(repo, revision)
577 fctx = ctx.filectx(path)
578 return fctx.isbinary()
579
580 return _is_binary(repo_id, revision, path)
557
581
558 @reraise_safe_exceptions
582 @reraise_safe_exceptions
559 def in_largefiles_store(self, wire, sha):
583 def in_largefiles_store(self, wire, sha):
@@ -582,22 +606,25 b' class HgRemote(object):'
582
606
583 @reraise_safe_exceptions
607 @reraise_safe_exceptions
584 def lookup(self, wire, revision, both):
608 def lookup(self, wire, revision, both):
609 cache_on, context_uid, repo_id = self._cache_on(wire)
610 @self.region.conditional_cache_on_arguments(condition=cache_on)
611 def _lookup(_context_uid, _repo_id, _revision, _both):
585
612
586 repo = self._factory.repo(wire)
613 repo = self._factory.repo(wire)
587
614 rev = _revision
588 if isinstance(revision, int):
615 if isinstance(rev, int):
589 # NOTE(marcink):
616 # NOTE(marcink):
590 # since Mercurial doesn't support negative indexes properly
617 # since Mercurial doesn't support negative indexes properly
591 # we need to shift accordingly by one to get proper index, e.g
618 # we need to shift accordingly by one to get proper index, e.g
592 # repo[-1] => repo[-2]
619 # repo[-1] => repo[-2]
593 # repo[0] => repo[-1]
620 # repo[0] => repo[-1]
594 if revision <= 0:
621 if rev <= 0:
595 revision = revision + -1
622 rev = rev + -1
596 try:
623 try:
597 ctx = self._get_ctx(repo, revision)
624 ctx = self._get_ctx(repo, rev)
598 except (TypeError, RepoLookupError) as e:
625 except (TypeError, RepoLookupError) as e:
599 e._org_exc_tb = traceback.format_exc()
626 e._org_exc_tb = traceback.format_exc()
600 raise exceptions.LookupException(e)(revision)
627 raise exceptions.LookupException(e)(rev)
601 except LookupError as e:
628 except LookupError as e:
602 e._org_exc_tb = traceback.format_exc()
629 e._org_exc_tb = traceback.format_exc()
603 raise exceptions.LookupException(e)(e.name)
630 raise exceptions.LookupException(e)(e.name)
@@ -608,21 +635,7 b' class HgRemote(object):'
608 ctx = repo[ctx.hex()]
635 ctx = repo[ctx.hex()]
609 return ctx.hex(), ctx.rev()
636 return ctx.hex(), ctx.rev()
610
637
611 @reraise_safe_exceptions
638 return _lookup(context_uid, repo_id, revision, both)
612 def pull(self, wire, url, commit_ids=None):
613 repo = self._factory.repo(wire)
614 # Disable any prompts for this repo
615 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
616
617 remote = peer(repo, {}, url)
618 # Disable any prompts for this remote
619 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
620
621 if commit_ids:
622 commit_ids = [bin(commit_id) for commit_id in commit_ids]
623
624 return exchange.pull(
625 repo, remote, heads=commit_ids, force=None).cgresult
626
639
627 @reraise_safe_exceptions
640 @reraise_safe_exceptions
628 def sync_push(self, wire, url):
641 def sync_push(self, wire, url):
@@ -649,11 +662,17 b' class HgRemote(object):'
649 return ctx.rev()
662 return ctx.rev()
650
663
651 @reraise_safe_exceptions
664 @reraise_safe_exceptions
652 def rev_range(self, wire, filter):
665 def rev_range(self, wire, commit_filter):
666 cache_on, context_uid, repo_id = self._cache_on(wire)
667
668 @self.region.conditional_cache_on_arguments(condition=cache_on)
669 def _rev_range(_context_uid, _repo_id, _filter):
653 repo = self._factory.repo(wire)
670 repo = self._factory.repo(wire)
654 revisions = [rev for rev in revrange(repo, filter)]
671 revisions = [rev for rev in revrange(repo, commit_filter)]
655 return revisions
672 return revisions
656
673
674 return _rev_range(context_uid, repo_id, sorted(commit_filter))
675
657 @reraise_safe_exceptions
676 @reraise_safe_exceptions
658 def rev_range_hash(self, wire, node):
677 def rev_range_hash(self, wire, node):
659 repo = self._factory.repo(wire)
678 repo = self._factory.repo(wire)
@@ -684,13 +703,6 b' class HgRemote(object):'
684 return list(repo.revs(rev_spec, *args))
703 return list(repo.revs(rev_spec, *args))
685
704
686 @reraise_safe_exceptions
705 @reraise_safe_exceptions
687 def strip(self, wire, revision, update, backup):
688 repo = self._factory.repo(wire)
689 ctx = self._get_ctx(repo, revision)
690 hgext_strip(
691 repo.baseui, repo, ctx.node(), update=update, backup=backup)
692
693 @reraise_safe_exceptions
694 def verify(self, wire,):
706 def verify(self, wire,):
695 repo = self._factory.repo(wire)
707 repo = self._factory.repo(wire)
696 baseui = self._factory._create_config(wire['config'])
708 baseui = self._factory._create_config(wire['config'])
@@ -706,25 +718,32 b' class HgRemote(object):'
706 return output.getvalue()
718 return output.getvalue()
707
719
708 @reraise_safe_exceptions
720 @reraise_safe_exceptions
709 def tag(self, wire, name, revision, message, local, user,
721 def hg_update_cache(self, wire,):
710 tag_time, tag_timezone):
711 repo = self._factory.repo(wire)
722 repo = self._factory.repo(wire)
712 ctx = self._get_ctx(repo, revision)
723 baseui = self._factory._create_config(wire['config'])
713 node = ctx.node()
724 baseui.setconfig('ui', 'quiet', 'false')
725 output = io.BytesIO()
714
726
715 date = (tag_time, tag_timezone)
727 def write(data, **unused_kwargs):
716 try:
728 output.write(data)
717 hg_tag.tag(repo, name, node, message, local, user, date)
729 baseui.write = write
718 except Abort as e:
730
719 log.exception("Tag operation aborted")
731 repo.ui = baseui
720 # Exception can contain unicode which we convert
732 with repo.wlock(), repo.lock():
721 raise exceptions.AbortException(e)(repr(e))
733 repo.updatecaches(full=True)
734
735 return output.getvalue()
722
736
723 @reraise_safe_exceptions
737 @reraise_safe_exceptions
724 def tags(self, wire):
738 def tags(self, wire):
739 cache_on, context_uid, repo_id = self._cache_on(wire)
740 @self.region.conditional_cache_on_arguments(condition=cache_on)
741 def _tags(_context_uid, _repo_id):
725 repo = self._factory.repo(wire)
742 repo = self._factory.repo(wire)
726 return repo.tags()
743 return repo.tags()
727
744
745 return _tags(context_uid, repo_id)
746
728 @reraise_safe_exceptions
747 @reraise_safe_exceptions
729 def update(self, wire, node=None, clean=False):
748 def update(self, wire, node=None, clean=False):
730 repo = self._factory.repo(wire)
749 repo = self._factory.repo(wire)
@@ -744,24 +763,6 b' class HgRemote(object):'
744 return output.getvalue()
763 return output.getvalue()
745
764
746 @reraise_safe_exceptions
765 @reraise_safe_exceptions
747 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None,
748 hooks=True):
749 repo = self._factory.repo(wire)
750 baseui = self._factory._create_config(wire['config'], hooks=hooks)
751
752 # Mercurial internally has a lot of logic that checks ONLY if
753 # option is defined, we just pass those if they are defined then
754 opts = {}
755 if bookmark:
756 opts['bookmark'] = bookmark
757 if branch:
758 opts['branch'] = branch
759 if revision:
760 opts['rev'] = revision
761
762 commands.pull(baseui, repo, source, **opts)
763
764 @reraise_safe_exceptions
765 def heads(self, wire, branch=None):
766 def heads(self, wire, branch=None):
766 repo = self._factory.repo(wire)
767 repo = self._factory.repo(wire)
767 baseui = self._factory._create_config(wire['config'])
768 baseui = self._factory._create_config(wire['config'])
@@ -788,14 +789,130 b' class HgRemote(object):'
788 return hex(a)
789 return hex(a)
789
790
790 @reraise_safe_exceptions
791 @reraise_safe_exceptions
791 def push(self, wire, revisions, dest_path, hooks=True,
792 def clone(self, wire, source, dest, update_after_clone=False, hooks=True):
792 push_branches=False):
793 baseui = self._factory._create_config(wire["config"], hooks=hooks)
794 clone(baseui, source, dest, noupdate=not update_after_clone)
795
796 @reraise_safe_exceptions
797 def commitctx(self, wire, message, parents, commit_time, commit_timezone, user, files, extra, removed, updated):
798
799 repo = self._factory.repo(wire)
800 baseui = self._factory._create_config(wire['config'])
801 publishing = baseui.configbool('phases', 'publish')
802 if publishing:
803 new_commit = 'public'
804 else:
805 new_commit = 'draft'
806
807 def _filectxfn(_repo, ctx, path):
808 """
809 Marks given path as added/changed/removed in a given _repo. This is
810 for internal mercurial commit function.
811 """
812
813 # check if this path is removed
814 if path in removed:
815 # returning None is a way to mark node for removal
816 return None
817
818 # check if this path is added
819 for node in updated:
820 if node['path'] == path:
821 return memfilectx(
822 _repo,
823 changectx=ctx,
824 path=node['path'],
825 data=node['content'],
826 islink=False,
827 isexec=bool(node['mode'] & stat.S_IXUSR),
828 copysource=False)
829
830 raise exceptions.AbortException()(
831 "Given path haven't been marked as added, "
832 "changed or removed (%s)" % path)
833
834 with repo.ui.configoverride({('phases', 'new-commit'): new_commit}):
835
836 commit_ctx = memctx(
837 repo=repo,
838 parents=parents,
839 text=message,
840 files=files,
841 filectxfn=_filectxfn,
842 user=user,
843 date=(commit_time, commit_timezone),
844 extra=extra)
845
846 n = repo.commitctx(commit_ctx)
847 new_id = hex(n)
848
849 return new_id
850
851 @reraise_safe_exceptions
852 def pull(self, wire, url, commit_ids=None):
853 repo = self._factory.repo(wire)
854 # Disable any prompts for this repo
855 repo.ui.setconfig('ui', 'interactive', 'off', '-y')
856
857 remote = peer(repo, {}, url)
858 # Disable any prompts for this remote
859 remote.ui.setconfig('ui', 'interactive', 'off', '-y')
860
861 if commit_ids:
862 commit_ids = [bin(commit_id) for commit_id in commit_ids]
863
864 return exchange.pull(
865 repo, remote, heads=commit_ids, force=None).cgresult
866
867 @reraise_safe_exceptions
868 def pull_cmd(self, wire, source, bookmark=None, branch=None, revision=None, hooks=True):
869 repo = self._factory.repo(wire)
870 baseui = self._factory._create_config(wire['config'], hooks=hooks)
871
872 # Mercurial internally has a lot of logic that checks ONLY if
873 # option is defined, we just pass those if they are defined then
874 opts = {}
875 if bookmark:
876 opts['bookmark'] = bookmark
877 if branch:
878 opts['branch'] = branch
879 if revision:
880 opts['rev'] = revision
881
882 commands.pull(baseui, repo, source, **opts)
883
884 @reraise_safe_exceptions
885 def push(self, wire, revisions, dest_path, hooks=True, push_branches=False):
793 repo = self._factory.repo(wire)
886 repo = self._factory.repo(wire)
794 baseui = self._factory._create_config(wire['config'], hooks=hooks)
887 baseui = self._factory._create_config(wire['config'], hooks=hooks)
795 commands.push(baseui, repo, dest=dest_path, rev=revisions,
888 commands.push(baseui, repo, dest=dest_path, rev=revisions,
796 new_branch=push_branches)
889 new_branch=push_branches)
797
890
798 @reraise_safe_exceptions
891 @reraise_safe_exceptions
892 def strip(self, wire, revision, update, backup):
893 repo = self._factory.repo(wire)
894 ctx = self._get_ctx(repo, revision)
895 hgext_strip(
896 repo.baseui, repo, ctx.node(), update=update, backup=backup)
897
898 @reraise_safe_exceptions
899 def get_unresolved_files(self, wire):
900 repo = self._factory.repo(wire)
901
902 log.debug('Calculating unresolved files for repo: %s', repo)
903 output = io.BytesIO()
904
905 def write(data, **unused_kwargs):
906 output.write(data)
907
908 baseui = self._factory._create_config(wire['config'])
909 baseui.write = write
910
911 commands.resolve(baseui, repo, list=True)
912 unresolved = output.getvalue().splitlines(0)
913 return unresolved
914
915 @reraise_safe_exceptions
799 def merge(self, wire, revision):
916 def merge(self, wire, revision):
800 repo = self._factory.repo(wire)
917 repo = self._factory.repo(wire)
801 baseui = self._factory._create_config(wire['config'])
918 baseui = self._factory._create_config(wire['config'])
@@ -828,14 +945,31 b' class HgRemote(object):'
828 repo.ui.setconfig('ui', 'username', username)
945 repo.ui.setconfig('ui', 'username', username)
829 commands.commit(baseui, repo, message=message, close_branch=close_branch)
946 commands.commit(baseui, repo, message=message, close_branch=close_branch)
830
947
831
832 @reraise_safe_exceptions
948 @reraise_safe_exceptions
833 def rebase(self, wire, source=None, dest=None, abort=False):
949 def rebase(self, wire, source=None, dest=None, abort=False):
834 repo = self._factory.repo(wire)
950 repo = self._factory.repo(wire)
835 baseui = self._factory._create_config(wire['config'])
951 baseui = self._factory._create_config(wire['config'])
836 repo.ui.setconfig('ui', 'merge', 'internal:dump')
952 repo.ui.setconfig('ui', 'merge', 'internal:dump')
837 rebase.rebase(
953 # In case of sub repositories are used mercurial prompts the user in
838 baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
954 # case of merge conflicts or different sub repository sources. By
955 # setting the interactive flag to `False` mercurial doesn't prompt the
956 # used but instead uses a default value.
957 repo.ui.setconfig('ui', 'interactive', False)
958 rebase.rebase(baseui, repo, base=source, dest=dest, abort=abort, keep=not abort)
959
960 @reraise_safe_exceptions
961 def tag(self, wire, name, revision, message, local, user, tag_time, tag_timezone):
962 repo = self._factory.repo(wire)
963 ctx = self._get_ctx(repo, revision)
964 node = ctx.node()
965
966 date = (tag_time, tag_timezone)
967 try:
968 hg_tag.tag(repo, name, node, message, local, user, date)
969 except Abort as e:
970 log.exception("Tag operation aborted")
971 # Exception can contain unicode which we convert
972 raise exceptions.AbortException(e)(repr(e))
839
973
840 @reraise_safe_exceptions
974 @reraise_safe_exceptions
841 def bookmark(self, wire, bookmark, revision=None):
975 def bookmark(self, wire, bookmark, revision=None):
@@ -33,7 +33,6 b' import mercurial.node'
33 import simplejson as json
33 import simplejson as json
34
34
35 from vcsserver import exceptions, subprocessio, settings
35 from vcsserver import exceptions, subprocessio, settings
36 from vcsserver.hgcompat import get_ctx
37
36
38 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
39
38
@@ -81,6 +80,12 b' class HooksDummyClient(object):'
81 return getattr(hooks, hook_name)(extras)
80 return getattr(hooks, hook_name)(extras)
82
81
83
82
83 class HooksShadowRepoClient(object):
84
85 def __call__(self, hook_name, extras):
86 return {'output': '', 'status': 0}
87
88
84 class RemoteMessageWriter(object):
89 class RemoteMessageWriter(object):
85 """Writer base class."""
90 """Writer base class."""
86 def write(self, message):
91 def write(self, message):
@@ -141,9 +146,12 b' def _handle_exception(result):'
141
146
142
147
143 def _get_hooks_client(extras):
148 def _get_hooks_client(extras):
144 if 'hooks_uri' in extras:
149 hooks_uri = extras.get('hooks_uri')
145 protocol = extras.get('hooks_protocol')
150 is_shadow_repo = extras.get('is_shadow_repo')
151 if hooks_uri:
146 return HooksHttpClient(extras['hooks_uri'])
152 return HooksHttpClient(extras['hooks_uri'])
153 elif is_shadow_repo:
154 return HooksShadowRepoClient()
147 else:
155 else:
148 return HooksDummyClient(extras['hooks_module'])
156 return HooksDummyClient(extras['hooks_module'])
149
157
@@ -175,6 +183,7 b' def _extras_from_ui(ui):'
175
183
176
184
177 def _rev_range_hash(repo, node, check_heads=False):
185 def _rev_range_hash(repo, node, check_heads=False):
186 from vcsserver.hgcompat import get_ctx
178
187
179 commits = []
188 commits = []
180 revs = []
189 revs = []
@@ -194,6 +203,7 b' def _rev_range_hash(repo, node, check_he'
194
203
195
204
196 def _check_heads(repo, start, end, commits):
205 def _check_heads(repo, start, end, commits):
206 from vcsserver.hgcompat import get_ctx
197 changelog = repo.changelog
207 changelog = repo.changelog
198 parents = set()
208 parents = set()
199
209
@@ -384,6 +394,7 b' def post_push_ssh(ui, repo, node, **kwar'
384
394
385
395
386 def key_push(ui, repo, **kwargs):
396 def key_push(ui, repo, **kwargs):
397 from vcsserver.hgcompat import get_ctx
387 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
398 if kwargs['new'] != '0' and kwargs['namespace'] == 'bookmarks':
388 # store new bookmarks in our UI object propagated later to post_push
399 # store new bookmarks in our UI object propagated later to post_push
389 ui._rc_pushkey_branches = get_ctx(repo, kwargs['key']).bookmarks()
400 ui._rc_pushkey_branches = get_ctx(repo, kwargs['key']).bookmarks()
@@ -25,6 +25,7 b' import wsgiref.util'
25 import traceback
25 import traceback
26 import tempfile
26 import tempfile
27 from itertools import chain
27 from itertools import chain
28 from cStringIO import StringIO
28
29
29 import simplejson as json
30 import simplejson as json
30 import msgpack
31 import msgpack
@@ -32,7 +33,9 b' from pyramid.config import Configurator'
32 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
33 from pyramid.wsgi import wsgiapp
34 from pyramid.wsgi import wsgiapp
34 from pyramid.compat import configparser
35 from pyramid.compat import configparser
36 from pyramid.response import Response
35
37
38 from vcsserver.utils import safe_int
36
39
37 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
38
41
@@ -114,8 +117,8 b' def _string_setting(settings, name, defa'
114
117
115
118
116 class VCS(object):
119 class VCS(object):
117 def __init__(self, locale=None, cache_config=None):
120 def __init__(self, locale_conf=None, cache_config=None):
118 self.locale = locale
121 self.locale = locale_conf
119 self.cache_config = cache_config
122 self.cache_config = cache_config
120 self._configure_locale()
123 self._configure_locale()
121
124
@@ -232,8 +235,8 b' class HTTPApplication(object):'
232 self.global_config = global_config
235 self.global_config = global_config
233 self.config.include('vcsserver.lib.rc_cache')
236 self.config.include('vcsserver.lib.rc_cache')
234
237
235 locale = settings.get('locale', '') or 'en_US.UTF-8'
238 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
236 vcs = VCS(locale=locale, cache_config=settings)
239 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
237 self._remotes = {
240 self._remotes = {
238 'hg': vcs._hg_remote,
241 'hg': vcs._hg_remote,
239 'git': vcs._git_remote,
242 'git': vcs._git_remote,
@@ -290,15 +293,15 b' class HTTPApplication(object):'
290 _string_setting(
293 _string_setting(
291 settings,
294 settings,
292 'rc_cache.repo_object.backend',
295 'rc_cache.repo_object.backend',
293 'dogpile.cache.rc.memory_lru')
296 'dogpile.cache.rc.file_namespace', lower=False)
294 _int_setting(
297 _int_setting(
295 settings,
298 settings,
296 'rc_cache.repo_object.expiration_time',
299 'rc_cache.repo_object.expiration_time',
297 300)
300 30 * 24 * 60 * 60)
298 _int_setting(
301 _string_setting(
299 settings,
302 settings,
300 'rc_cache.repo_object.max_size',
303 'rc_cache.repo_object.arguments.filename',
301 1024)
304 os.path.join(default_cache_dir, 'vcsserver_cache_1'), lower=False)
302
305
303 def _configure(self):
306 def _configure(self):
304 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
307 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
@@ -307,7 +310,14 b' class HTTPApplication(object):'
307 self.config.add_route('status', '/status')
310 self.config.add_route('status', '/status')
308 self.config.add_route('hg_proxy', '/proxy/hg')
311 self.config.add_route('hg_proxy', '/proxy/hg')
309 self.config.add_route('git_proxy', '/proxy/git')
312 self.config.add_route('git_proxy', '/proxy/git')
313
314 # rpc methods
310 self.config.add_route('vcs', '/{backend}')
315 self.config.add_route('vcs', '/{backend}')
316
317 # streaming rpc remote methods
318 self.config.add_route('vcs_stream', '/{backend}/stream')
319
320 # vcs operations clone/push as streaming
311 self.config.add_route('stream_git', '/stream/git/*repo_name')
321 self.config.add_route('stream_git', '/stream/git/*repo_name')
312 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
322 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
313
323
@@ -318,6 +328,8 b' class HTTPApplication(object):'
318 self.config.add_view(self.git_proxy(), route_name='git_proxy')
328 self.config.add_view(self.git_proxy(), route_name='git_proxy')
319 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
329 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
320 vcs_view=self._remotes)
330 vcs_view=self._remotes)
331 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
332 vcs_view=self._remotes)
321
333
322 self.config.add_view(self.hg_stream(), route_name='stream_hg')
334 self.config.add_view(self.hg_stream(), route_name='stream_hg')
323 self.config.add_view(self.git_stream(), route_name='stream_git')
335 self.config.add_view(self.git_stream(), route_name='stream_git')
@@ -329,17 +341,20 b' class HTTPApplication(object):'
329 self.config.add_view(self.handle_vcs_exception, context=Exception)
341 self.config.add_view(self.handle_vcs_exception, context=Exception)
330
342
331 self.config.add_tween(
343 self.config.add_tween(
332 'vcsserver.tweens.RequestWrapperTween',
344 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
333 )
345 )
346 self.config.add_request_method(
347 'vcsserver.lib.request_counter.get_request_counter',
348 'request_count')
334
349
335 def wsgi_app(self):
350 def wsgi_app(self):
336 return self.config.make_wsgi_app()
351 return self.config.make_wsgi_app()
337
352
338 def vcs_view(self, request):
353 def _vcs_view_params(self, request):
339 remote = self._remotes[request.matchdict['backend']]
354 remote = self._remotes[request.matchdict['backend']]
340 payload = msgpack.unpackb(request.body, use_list=True)
355 payload = msgpack.unpackb(request.body, use_list=True)
341 method = payload.get('method')
356 method = payload.get('method')
342 params = payload.get('params')
357 params = payload['params']
343 wire = params.get('wire')
358 wire = params.get('wire')
344 args = params.get('args')
359 args = params.get('args')
345 kwargs = params.get('kwargs')
360 kwargs = params.get('kwargs')
@@ -351,9 +366,28 b' class HTTPApplication(object):'
351 except KeyError:
366 except KeyError:
352 pass
367 pass
353 args.insert(0, wire)
368 args.insert(0, wire)
369 repo_state_uid = wire.get('repo_state_uid') if wire else None
354
370
355 log.debug('method called:%s with kwargs:%s context_uid: %s',
371 # NOTE(marcink): trading complexity for slight performance
356 method, kwargs, context_uid)
372 if log.isEnabledFor(logging.DEBUG):
373 no_args_methods = [
374 'archive_repo'
375 ]
376 if method in no_args_methods:
377 call_args = ''
378 else:
379 call_args = args[1:]
380
381 log.debug('method requested:%s with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
382 method, call_args, kwargs, context_uid, repo_state_uid)
383
384 return payload, remote, method, args, kwargs
385
386 def vcs_view(self, request):
387
388 payload, remote, method, args, kwargs = self._vcs_view_params(request)
389 payload_id = payload.get('id')
390
357 try:
391 try:
358 resp = getattr(remote, method)(*args, **kwargs)
392 resp = getattr(remote, method)(*args, **kwargs)
359 except Exception as e:
393 except Exception as e:
@@ -380,7 +414,7 b' class HTTPApplication(object):'
380 type_ = None
414 type_ = None
381
415
382 resp = {
416 resp = {
383 'id': payload.get('id'),
417 'id': payload_id,
384 'error': {
418 'error': {
385 'message': e.message,
419 'message': e.message,
386 'traceback': tb_info,
420 'traceback': tb_info,
@@ -395,12 +429,36 b' class HTTPApplication(object):'
395 pass
429 pass
396 else:
430 else:
397 resp = {
431 resp = {
398 'id': payload.get('id'),
432 'id': payload_id,
399 'result': resp
433 'result': resp
400 }
434 }
401
435
402 return resp
436 return resp
403
437
438 def vcs_stream_view(self, request):
439 payload, remote, method, args, kwargs = self._vcs_view_params(request)
440 # this method has a stream: marker we remove it here
441 method = method.split('stream:')[-1]
442 chunk_size = safe_int(payload.get('chunk_size')) or 4096
443
444 try:
445 resp = getattr(remote, method)(*args, **kwargs)
446 except Exception as e:
447 raise
448
449 def get_chunked_data(method_resp):
450 stream = StringIO(method_resp)
451 while 1:
452 chunk = stream.read(chunk_size)
453 if not chunk:
454 break
455 yield chunk
456
457 response = Response(app_iter=get_chunked_data(resp))
458 response.content_type = 'application/octet-stream'
459
460 return response
461
404 def status_view(self, request):
462 def status_view(self, request):
405 import vcsserver
463 import vcsserver
406 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
464 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
@@ -410,23 +468,31 b' class HTTPApplication(object):'
410 import vcsserver
468 import vcsserver
411
469
412 payload = msgpack.unpackb(request.body, use_list=True)
470 payload = msgpack.unpackb(request.body, use_list=True)
471 server_config, app_config = {}, {}
413
472
414 try:
473 try:
415 path = self.global_config['__file__']
474 path = self.global_config['__file__']
416 config = configparser.ConfigParser()
475 config = configparser.RawConfigParser()
476
417 config.read(path)
477 config.read(path)
418 parsed_ini = config
478
419 if parsed_ini.has_section('server:main'):
479 if config.has_section('server:main'):
420 parsed_ini = dict(parsed_ini.items('server:main'))
480 server_config = dict(config.items('server:main'))
481 if config.has_section('app:main'):
482 app_config = dict(config.items('app:main'))
483
421 except Exception:
484 except Exception:
422 log.exception('Failed to read .ini file for display')
485 log.exception('Failed to read .ini file for display')
423 parsed_ini = {}
486
487 environ = os.environ.items()
424
488
425 resp = {
489 resp = {
426 'id': payload.get('id'),
490 'id': payload.get('id'),
427 'result': dict(
491 'result': dict(
428 version=vcsserver.__version__,
492 version=vcsserver.__version__,
429 config=parsed_ini,
493 config=server_config,
494 app_config=app_config,
495 environ=environ,
430 payload=payload,
496 payload=payload,
431 )
497 )
432 }
498 }
@@ -434,14 +500,13 b' class HTTPApplication(object):'
434
500
435 def _msgpack_renderer_factory(self, info):
501 def _msgpack_renderer_factory(self, info):
436 def _render(value, system):
502 def _render(value, system):
437 value = msgpack.packb(value)
438 request = system.get('request')
503 request = system.get('request')
439 if request is not None:
504 if request is not None:
440 response = request.response
505 response = request.response
441 ct = response.content_type
506 ct = response.content_type
442 if ct == response.default_content_type:
507 if ct == response.default_content_type:
443 response.content_type = 'application/x-msgpack'
508 response.content_type = 'application/x-msgpack'
444 return value
509 return msgpack.packb(value)
445 return _render
510 return _render
446
511
447 def set_env_from_config(self, environ, config):
512 def set_env_from_config(self, environ, config):
@@ -22,10 +22,23 b' register_backend('
22 "dogpile.cache.rc.memory_lru", "vcsserver.lib.rc_cache.backends",
22 "dogpile.cache.rc.memory_lru", "vcsserver.lib.rc_cache.backends",
23 "LRUMemoryBackend")
23 "LRUMemoryBackend")
24
24
25 register_backend(
26 "dogpile.cache.rc.file_namespace", "vcsserver.lib.rc_cache.backends",
27 "FileNamespaceBackend")
28
29 register_backend(
30 "dogpile.cache.rc.redis", "vcsserver.lib.rc_cache.backends",
31 "RedisPickleBackend")
32
33 register_backend(
34 "dogpile.cache.rc.redis_msgpack", "vcsserver.lib.rc_cache.backends",
35 "RedisMsgPackBackend")
36
37
25 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
26
39
27 from . import region_meta
40 from . import region_meta
28 from .util import key_generator, get_default_cache_settings, make_region
41 from .utils import (get_default_cache_settings, backend_key_generator, make_region)
29
42
30
43
31 def configure_dogpile_cache(settings):
44 def configure_dogpile_cache(settings):
@@ -46,13 +59,12 b' def configure_dogpile_cache(settings):'
46 for region_name in avail_regions:
59 for region_name in avail_regions:
47 new_region = make_region(
60 new_region = make_region(
48 name=region_name,
61 name=region_name,
49 function_key_generator=key_generator
62 function_key_generator=None
50 )
63 )
51
64
52 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
65 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
53
66 new_region.function_key_generator = backend_key_generator(new_region.actual_backend)
54 log.debug('dogpile: registering a new region %s[%s]',
67 log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__)
55 region_name, new_region.__dict__)
56 region_meta.dogpile_cache_regions[region_name] = new_region
68 region_meta.dogpile_cache_regions[region_name] = new_region
57
69
58
70
@@ -15,9 +15,20 b''
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import time
19 import errno
18 import logging
20 import logging
19
21
22 import msgpack
23 import redis
24
25 from dogpile.cache.api import CachedValue
20 from dogpile.cache.backends import memory as memory_backend
26 from dogpile.cache.backends import memory as memory_backend
27 from dogpile.cache.backends import file as file_backend
28 from dogpile.cache.backends import redis as redis_backend
29 from dogpile.cache.backends.file import NO_VALUE, compat, FileLock
30 from dogpile.cache.util import memoized_property
31
21 from vcsserver.lib.memory_lru_dict import LRUDict, LRUDictDebug
32 from vcsserver.lib.memory_lru_dict import LRUDict, LRUDictDebug
22
33
23
34
@@ -27,6 +38,7 b' log = logging.getLogger(__name__)'
27
38
28
39
29 class LRUMemoryBackend(memory_backend.MemoryBackend):
40 class LRUMemoryBackend(memory_backend.MemoryBackend):
41 key_prefix = 'lru_mem_backend'
30 pickle_values = False
42 pickle_values = False
31
43
32 def __init__(self, arguments):
44 def __init__(self, arguments):
@@ -49,3 +61,193 b' class LRUMemoryBackend(memory_backend.Me'
49 def delete_multi(self, keys):
61 def delete_multi(self, keys):
50 for key in keys:
62 for key in keys:
51 self.delete(key)
63 self.delete(key)
64
65
66 class PickleSerializer(object):
67
68 def _dumps(self, value, safe=False):
69 try:
70 return compat.pickle.dumps(value)
71 except Exception:
72 if safe:
73 return NO_VALUE
74 else:
75 raise
76
77 def _loads(self, value, safe=True):
78 try:
79 return compat.pickle.loads(value)
80 except Exception:
81 if safe:
82 return NO_VALUE
83 else:
84 raise
85
86
87 class MsgPackSerializer(object):
88
89 def _dumps(self, value, safe=False):
90 try:
91 return msgpack.packb(value)
92 except Exception:
93 if safe:
94 return NO_VALUE
95 else:
96 raise
97
98 def _loads(self, value, safe=True):
99 """
100 pickle maintained the `CachedValue` wrapper of the tuple
101 msgpack does not, so it must be added back in.
102 """
103 try:
104 value = msgpack.unpackb(value, use_list=False)
105 return CachedValue(*value)
106 except Exception:
107 if safe:
108 return NO_VALUE
109 else:
110 raise
111
112
113 import fcntl
114 flock_org = fcntl.flock
115
116
117 class CustomLockFactory(FileLock):
118
119 pass
120
121
122 class FileNamespaceBackend(PickleSerializer, file_backend.DBMBackend):
123 key_prefix = 'file_backend'
124
125 def __init__(self, arguments):
126 arguments['lock_factory'] = CustomLockFactory
127 super(FileNamespaceBackend, self).__init__(arguments)
128
129 def __repr__(self):
130 return '{} `{}`'.format(self.__class__, self.filename)
131
132 def list_keys(self, prefix=''):
133 prefix = '{}:{}'.format(self.key_prefix, prefix)
134
135 def cond(v):
136 if not prefix:
137 return True
138
139 if v.startswith(prefix):
140 return True
141 return False
142
143 with self._dbm_file(True) as dbm:
144
145 return filter(cond, dbm.keys())
146
147 def get_store(self):
148 return self.filename
149
150 def get(self, key):
151 with self._dbm_file(False) as dbm:
152 if hasattr(dbm, 'get'):
153 value = dbm.get(key, NO_VALUE)
154 else:
155 # gdbm objects lack a .get method
156 try:
157 value = dbm[key]
158 except KeyError:
159 value = NO_VALUE
160 if value is not NO_VALUE:
161 value = self._loads(value)
162 return value
163
164 def set(self, key, value):
165 with self._dbm_file(True) as dbm:
166 dbm[key] = self._dumps(value)
167
168 def set_multi(self, mapping):
169 with self._dbm_file(True) as dbm:
170 for key, value in mapping.items():
171 dbm[key] = self._dumps(value)
172
173
174 class BaseRedisBackend(redis_backend.RedisBackend):
175
176 def _create_client(self):
177 args = {}
178
179 if self.url is not None:
180 args.update(url=self.url)
181
182 else:
183 args.update(
184 host=self.host, password=self.password,
185 port=self.port, db=self.db
186 )
187
188 connection_pool = redis.ConnectionPool(**args)
189
190 return redis.StrictRedis(connection_pool=connection_pool)
191
192 def list_keys(self, prefix=''):
193 prefix = '{}:{}*'.format(self.key_prefix, prefix)
194 return self.client.keys(prefix)
195
196 def get_store(self):
197 return self.client.connection_pool
198
199 def get(self, key):
200 value = self.client.get(key)
201 if value is None:
202 return NO_VALUE
203 return self._loads(value)
204
205 def get_multi(self, keys):
206 if not keys:
207 return []
208 values = self.client.mget(keys)
209 loads = self._loads
210 return [
211 loads(v) if v is not None else NO_VALUE
212 for v in values]
213
214 def set(self, key, value):
215 if self.redis_expiration_time:
216 self.client.setex(key, self.redis_expiration_time,
217 self._dumps(value))
218 else:
219 self.client.set(key, self._dumps(value))
220
221 def set_multi(self, mapping):
222 dumps = self._dumps
223 mapping = dict(
224 (k, dumps(v))
225 for k, v in mapping.items()
226 )
227
228 if not self.redis_expiration_time:
229 self.client.mset(mapping)
230 else:
231 pipe = self.client.pipeline()
232 for key, value in mapping.items():
233 pipe.setex(key, self.redis_expiration_time, value)
234 pipe.execute()
235
236 def get_mutex(self, key):
237 u = redis_backend.u
238 if self.distributed_lock:
239 lock_key = u('_lock_{0}').format(key)
240 log.debug('Trying to acquire Redis lock for key %s', lock_key)
241 return self.client.lock(lock_key, self.lock_timeout, self.lock_sleep)
242 else:
243 return None
244
245
246 class RedisPickleBackend(PickleSerializer, BaseRedisBackend):
247 key_prefix = 'redis_pickle_backend'
248 pass
249
250
251 class RedisMsgPackBackend(MsgPackSerializer, BaseRedisBackend):
252 key_prefix = 'redis_msgpack_backend'
253 pass
@@ -18,10 +18,13 b''
18 import os
18 import os
19 import logging
19 import logging
20 import functools
20 import functools
21 from decorator import decorate
22
23 from dogpile.cache import CacheRegion
24 from dogpile.cache.util import compat
21
25
22 from vcsserver.utils import safe_str, sha1
26 from vcsserver.utils import safe_str, sha1
23 from dogpile.cache import CacheRegion
27
24 from dogpile.cache.util import compat
25
28
26 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
27
30
@@ -45,28 +48,35 b' class RhodeCodeCacheRegion(CacheRegion):'
45 if function_key_generator is None:
48 if function_key_generator is None:
46 function_key_generator = self.function_key_generator
49 function_key_generator = self.function_key_generator
47
50
48 def decorator(fn):
51 def get_or_create_for_user_func(key_generator, user_func, *arg, **kw):
49 if to_str is compat.string_type:
50 # backwards compatible
51 key_generator = function_key_generator(namespace, fn)
52 else:
53 key_generator = function_key_generator(namespace, fn, to_str=to_str)
54
55 @functools.wraps(fn)
56 def decorate(*arg, **kw):
57 key = key_generator(*arg, **kw)
58
59 @functools.wraps(fn)
60 def creator():
61 return fn(*arg, **kw)
62
52
63 if not condition:
53 if not condition:
64 return creator()
54 log.debug('Calling un-cached func:%s', user_func.func_name)
55 return user_func(*arg, **kw)
56
57 key = key_generator(*arg, **kw)
65
58
66 timeout = expiration_time() if expiration_time_is_callable \
59 timeout = expiration_time() if expiration_time_is_callable \
67 else expiration_time
60 else expiration_time
68
61
69 return self.get_or_create(key, creator, timeout, should_cache_fn)
62 log.debug('Calling cached fn:%s', user_func.func_name)
63 return self.get_or_create(key, user_func, timeout, should_cache_fn, (arg, kw))
64
65 def cache_decorator(user_func):
66 if to_str is compat.string_type:
67 # backwards compatible
68 key_generator = function_key_generator(namespace, user_func)
69 else:
70 key_generator = function_key_generator(namespace, user_func, to_str=to_str)
71
72 def refresh(*arg, **kw):
73 """
74 Like invalidate, but regenerates the value instead
75 """
76 key = key_generator(*arg, **kw)
77 value = user_func(*arg, **kw)
78 self.set(key, value)
79 return value
70
80
71 def invalidate(*arg, **kw):
81 def invalidate(*arg, **kw):
72 key = key_generator(*arg, **kw)
82 key = key_generator(*arg, **kw)
@@ -80,22 +90,19 b' class RhodeCodeCacheRegion(CacheRegion):'
80 key = key_generator(*arg, **kw)
90 key = key_generator(*arg, **kw)
81 return self.get(key)
91 return self.get(key)
82
92
83 def refresh(*arg, **kw):
93 user_func.set = set_
84 key = key_generator(*arg, **kw)
94 user_func.invalidate = invalidate
85 value = fn(*arg, **kw)
95 user_func.get = get
86 self.set(key, value)
96 user_func.refresh = refresh
87 return value
97 user_func.key_generator = key_generator
98 user_func.original = user_func
88
99
89 decorate.set = set_
100 # Use `decorate` to preserve the signature of :param:`user_func`.
90 decorate.invalidate = invalidate
91 decorate.refresh = refresh
92 decorate.get = get
93 decorate.original = fn
94 decorate.key_generator = key_generator
95
101
96 return decorate
102 return decorate(user_func, functools.partial(
103 get_or_create_for_user_func, key_generator))
97
104
98 return decorator
105 return cache_decorator
99
106
100
107
101 def make_region(*arg, **kw):
108 def make_region(*arg, **kw):
@@ -110,7 +117,7 b' def get_default_cache_settings(settings,'
110 if key.startswith(prefix):
117 if key.startswith(prefix):
111 name = key.split(prefix)[1].strip()
118 name = key.split(prefix)[1].strip()
112 val = settings[key]
119 val = settings[key]
113 if isinstance(val, basestring):
120 if isinstance(val, compat.string_types):
114 val = val.strip()
121 val = val.strip()
115 cache_settings[name] = val
122 cache_settings[name] = val
116 return cache_settings
123 return cache_settings
@@ -123,13 +130,23 b' def compute_key_from_params(*args):'
123 return sha1("_".join(map(safe_str, args)))
130 return sha1("_".join(map(safe_str, args)))
124
131
125
132
126 def key_generator(namespace, fn):
133 def backend_key_generator(backend):
134 """
135 Special wrapper that also sends over the backend to the key generator
136 """
137 def wrapper(namespace, fn):
138 return key_generator(backend, namespace, fn)
139 return wrapper
140
141
142 def key_generator(backend, namespace, fn):
127 fname = fn.__name__
143 fname = fn.__name__
128
144
129 def generate_key(*args):
145 def generate_key(*args):
130 namespace_pref = namespace or 'default'
146 backend_prefix = getattr(backend, 'key_prefix', None) or 'backend_prefix'
147 namespace_pref = namespace or 'default_namespace'
131 arg_key = compute_key_from_params(*args)
148 arg_key = compute_key_from_params(*args)
132 final_key = "{}:{}_{}".format(namespace_pref, fname, arg_key)
149 final_key = "{}:{}:{}_{}".format(backend_prefix, namespace_pref, fname, arg_key)
133
150
134 return final_key
151 return final_key
135
152
@@ -216,9 +216,6 b' class BufferedGenerator(object):'
216 except (GeneratorExit, StopIteration):
216 except (GeneratorExit, StopIteration):
217 pass
217 pass
218
218
219 def __del__(self):
220 self.close()
221
222 ####################
219 ####################
223 # Threaded reader's infrastructure.
220 # Threaded reader's infrastructure.
224 ####################
221 ####################
@@ -475,26 +472,23 b' class SubprocessIOChunker(object):'
475 self._closed = True
472 self._closed = True
476 try:
473 try:
477 self.process.terminate()
474 self.process.terminate()
478 except:
475 except Exception:
479 pass
476 pass
480 if self._close_input_fd:
477 if self._close_input_fd:
481 os.close(self._close_input_fd)
478 os.close(self._close_input_fd)
482 try:
479 try:
483 self.output.close()
480 self.output.close()
484 except:
481 except Exception:
485 pass
482 pass
486 try:
483 try:
487 self.error.close()
484 self.error.close()
488 except:
485 except Exception:
489 pass
486 pass
490 try:
487 try:
491 os.close(self.inputstream)
488 os.close(self.inputstream)
492 except:
489 except Exception:
493 pass
490 pass
494
491
495 def __del__(self):
496 self.close()
497
498
492
499 def run_command(arguments, env=None):
493 def run_command(arguments, env=None):
500 """
494 """
@@ -506,18 +500,20 b' def run_command(arguments, env=None):'
506
500
507 cmd = arguments
501 cmd = arguments
508 log.debug('Running subprocessio command %s', cmd)
502 log.debug('Running subprocessio command %s', cmd)
503 proc = None
509 try:
504 try:
510 _opts = {'shell': False, 'fail_on_stderr': False}
505 _opts = {'shell': False, 'fail_on_stderr': False}
511 if env:
506 if env:
512 _opts.update({'env': env})
507 _opts.update({'env': env})
513 p = SubprocessIOChunker(cmd, **_opts)
508 proc = SubprocessIOChunker(cmd, **_opts)
514 stdout = ''.join(p)
509 return ''.join(proc), ''.join(proc.error)
515 stderr = ''.join(''.join(p.error))
516 except (EnvironmentError, OSError) as err:
510 except (EnvironmentError, OSError) as err:
517 cmd = ' '.join(cmd) # human friendly CMD
511 cmd = ' '.join(cmd) # human friendly CMD
518 tb_err = ("Couldn't run subprocessio command (%s).\n"
512 tb_err = ("Couldn't run subprocessio command (%s).\n"
519 "Original error was:%s\n" % (cmd, err))
513 "Original error was:%s\n" % (cmd, err))
520 log.exception(tb_err)
514 log.exception(tb_err)
521 raise Exception(tb_err)
515 raise Exception(tb_err)
516 finally:
517 if proc:
518 proc.close()
522
519
523 return stdout, stderr
@@ -36,6 +36,7 b' import svn.repos'
36
36
37 from vcsserver import svn_diff, exceptions, subprocessio, settings
37 from vcsserver import svn_diff, exceptions, subprocessio, settings
38 from vcsserver.base import RepoFactory, raise_from_original
38 from vcsserver.base import RepoFactory, raise_from_original
39 from vcsserver.vcs_base import RemoteBase
39
40
40 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
41
42
@@ -97,24 +98,9 b' class SubversionFactory(RepoFactory):'
97 def repo(self, wire, create=False, compatible_version=None):
98 def repo(self, wire, create=False, compatible_version=None):
98 """
99 """
99 Get a repository instance for the given path.
100 Get a repository instance for the given path.
100
101 Uses internally the low level beaker API since the decorators introduce
102 significant overhead.
103 """
101 """
104 region = self._cache_region
105 context = wire.get('context', None)
106 repo_path = wire.get('path', '')
107 context_uid = '{}'.format(context)
108 cache = wire.get('cache', True)
109 cache_on = context and cache
110
111 @region.conditional_cache_on_arguments(condition=cache_on)
112 def create_new_repo(_repo_type, _repo_path, _context_uid, compatible_version_id):
113 return self._create_repo(wire, create, compatible_version)
102 return self._create_repo(wire, create, compatible_version)
114
103
115 return create_new_repo(self.repo_type, repo_path, context_uid,
116 compatible_version)
117
118
104
119 NODE_TYPE_MAPPING = {
105 NODE_TYPE_MAPPING = {
120 svn.core.svn_node_file: 'file',
106 svn.core.svn_node_file: 'file',
@@ -122,7 +108,7 b' NODE_TYPE_MAPPING = {'
122 }
108 }
123
109
124
110
125 class SvnRemote(object):
111 class SvnRemote(RemoteBase):
126
112
127 def __init__(self, factory, hg_factory=None):
113 def __init__(self, factory, hg_factory=None):
128 self._factory = factory
114 self._factory = factory
@@ -141,7 +127,6 b' class SvnRemote(object):'
141
127
142 @reraise_safe_exceptions
128 @reraise_safe_exceptions
143 def is_empty(self, wire):
129 def is_empty(self, wire):
144 repo = self._factory.repo(wire)
145
130
146 try:
131 try:
147 return self.lookup(wire, -1) == 0
132 return self.lookup(wire, -1) == 0
@@ -219,9 +204,14 b' class SvnRemote(object):'
219 return start_rev, end_rev
204 return start_rev, end_rev
220
205
221 def revision_properties(self, wire, revision):
206 def revision_properties(self, wire, revision):
207
208 cache_on, context_uid, repo_id = self._cache_on(wire)
209 @self.region.conditional_cache_on_arguments(condition=cache_on)
210 def _revision_properties(_repo_id, _revision):
222 repo = self._factory.repo(wire)
211 repo = self._factory.repo(wire)
223 fs_ptr = svn.repos.fs(repo)
212 fs_ptr = svn.repos.fs(repo)
224 return svn.fs.revision_proplist(fs_ptr, revision)
213 return svn.fs.revision_proplist(fs_ptr, revision)
214 return _revision_properties(repo_id, revision)
225
215
226 def revision_changes(self, wire, revision):
216 def revision_changes(self, wire, revision):
227
217
@@ -267,7 +257,11 b' class SvnRemote(object):'
267 }
257 }
268 return changes
258 return changes
269
259
260 @reraise_safe_exceptions
270 def node_history(self, wire, path, revision, limit):
261 def node_history(self, wire, path, revision, limit):
262 cache_on, context_uid, repo_id = self._cache_on(wire)
263 @self.region.conditional_cache_on_arguments(condition=cache_on)
264 def _assert_correct_path(_context_uid, _repo_id, _path, _revision, _limit):
271 cross_copies = False
265 cross_copies = False
272 repo = self._factory.repo(wire)
266 repo = self._factory.repo(wire)
273 fsobj = svn.repos.fs(repo)
267 fsobj = svn.repos.fs(repo)
@@ -283,12 +277,17 b' class SvnRemote(object):'
283 break
277 break
284 history = svn.fs.history_prev(history, cross_copies)
278 history = svn.fs.history_prev(history, cross_copies)
285 return history_revisions
279 return history_revisions
280 return _assert_correct_path(context_uid, repo_id, path, revision, limit)
286
281
287 def node_properties(self, wire, path, revision):
282 def node_properties(self, wire, path, revision):
283 cache_on, context_uid, repo_id = self._cache_on(wire)
284 @self.region.conditional_cache_on_arguments(condition=cache_on)
285 def _node_properties(_repo_id, _path, _revision):
288 repo = self._factory.repo(wire)
286 repo = self._factory.repo(wire)
289 fsobj = svn.repos.fs(repo)
287 fsobj = svn.repos.fs(repo)
290 rev_root = svn.fs.revision_root(fsobj, revision)
288 rev_root = svn.fs.revision_root(fsobj, revision)
291 return svn.fs.node_proplist(rev_root, path)
289 return svn.fs.node_proplist(rev_root, path)
290 return _node_properties(repo_id, path, revision)
292
291
293 def file_annotate(self, wire, path, revision):
292 def file_annotate(self, wire, path, revision):
294 abs_path = 'file://' + urllib.pathname2url(
293 abs_path = 'file://' + urllib.pathname2url(
@@ -317,27 +316,37 b' class SvnRemote(object):'
317
316
318 return annotations
317 return annotations
319
318
320 def get_node_type(self, wire, path, rev=None):
319 def get_node_type(self, wire, path, revision=None):
320
321 cache_on, context_uid, repo_id = self._cache_on(wire)
322 @self.region.conditional_cache_on_arguments(condition=cache_on)
323 def _get_node_type(_repo_id, _path, _revision):
321 repo = self._factory.repo(wire)
324 repo = self._factory.repo(wire)
322 fs_ptr = svn.repos.fs(repo)
325 fs_ptr = svn.repos.fs(repo)
323 if rev is None:
326 if _revision is None:
324 rev = svn.fs.youngest_rev(fs_ptr)
327 _revision = svn.fs.youngest_rev(fs_ptr)
325 root = svn.fs.revision_root(fs_ptr, rev)
328 root = svn.fs.revision_root(fs_ptr, _revision)
326 node = svn.fs.check_path(root, path)
329 node = svn.fs.check_path(root, path)
327 return NODE_TYPE_MAPPING.get(node, None)
330 return NODE_TYPE_MAPPING.get(node, None)
331 return _get_node_type(repo_id, path, revision)
328
332
329 def get_nodes(self, wire, path, revision=None):
333 def get_nodes(self, wire, path, revision=None):
334
335 cache_on, context_uid, repo_id = self._cache_on(wire)
336 @self.region.conditional_cache_on_arguments(condition=cache_on)
337 def _get_nodes(_repo_id, _path, _revision):
330 repo = self._factory.repo(wire)
338 repo = self._factory.repo(wire)
331 fsobj = svn.repos.fs(repo)
339 fsobj = svn.repos.fs(repo)
332 if revision is None:
340 if _revision is None:
333 revision = svn.fs.youngest_rev(fsobj)
341 _revision = svn.fs.youngest_rev(fsobj)
334 root = svn.fs.revision_root(fsobj, revision)
342 root = svn.fs.revision_root(fsobj, _revision)
335 entries = svn.fs.dir_entries(root, path)
343 entries = svn.fs.dir_entries(root, path)
336 result = []
344 result = []
337 for entry_path, entry_info in entries.iteritems():
345 for entry_path, entry_info in entries.iteritems():
338 result.append(
346 result.append(
339 (entry_path, NODE_TYPE_MAPPING.get(entry_info.kind, None)))
347 (entry_path, NODE_TYPE_MAPPING.get(entry_info.kind, None)))
340 return result
348 return result
349 return _get_nodes(repo_id, path, revision)
341
350
342 def get_file_content(self, wire, path, rev=None):
351 def get_file_content(self, wire, path, rev=None):
343 repo = self._factory.repo(wire)
352 repo = self._factory.repo(wire)
@@ -349,13 +358,18 b' class SvnRemote(object):'
349 return content.read()
358 return content.read()
350
359
351 def get_file_size(self, wire, path, revision=None):
360 def get_file_size(self, wire, path, revision=None):
361
362 cache_on, context_uid, repo_id = self._cache_on(wire)
363 @self.region.conditional_cache_on_arguments(condition=cache_on)
364 def _get_file_size(_repo_id, _path, _revision):
352 repo = self._factory.repo(wire)
365 repo = self._factory.repo(wire)
353 fsobj = svn.repos.fs(repo)
366 fsobj = svn.repos.fs(repo)
354 if revision is None:
367 if _revision is None:
355 revision = svn.fs.youngest_revision(fsobj)
368 _revision = svn.fs.youngest_revision(fsobj)
356 root = svn.fs.revision_root(fsobj, revision)
369 root = svn.fs.revision_root(fsobj, _revision)
357 size = svn.fs.file_length(root, path)
370 size = svn.fs.file_length(root, path)
358 return size
371 return size
372 return _get_file_size(repo_id, path, revision)
359
373
360 def create_repository(self, wire, compatible_version=None):
374 def create_repository(self, wire, compatible_version=None):
361 log.info('Creating Subversion repository in path "%s"', wire['path'])
375 log.info('Creating Subversion repository in path "%s"', wire['path'])
@@ -458,6 +472,17 b' class SvnRemote(object):'
458 return False
472 return False
459
473
460 @reraise_safe_exceptions
474 @reraise_safe_exceptions
475 def is_binary(self, wire, rev, path):
476 cache_on, context_uid, repo_id = self._cache_on(wire)
477
478 @self.region.conditional_cache_on_arguments(condition=cache_on)
479 def _is_binary(_repo_id, _rev, _path):
480 raw_bytes = self.get_file_content(wire, path, rev)
481 return raw_bytes and '\0' in raw_bytes
482
483 return _is_binary(repo_id, rev, path)
484
485 @reraise_safe_exceptions
461 def run_svn_command(self, wire, cmd, **opts):
486 def run_svn_command(self, wire, cmd, **opts):
462 path = wire.get('path', None)
487 path = wire.get('path', None)
463
488
@@ -673,7 +698,6 b' class SvnDiffer(object):'
673 return content.splitlines(True)
698 return content.splitlines(True)
674
699
675
700
676
677 class DiffChangeEditor(svn.delta.Editor):
701 class DiffChangeEditor(svn.delta.Editor):
678 """
702 """
679 Records changes between two given revisions
703 Records changes between two given revisions
@@ -61,7 +61,7 b' class TestGitFetch(object):'
61
61
62 with patch('dulwich.client.LocalGitClient.fetch') as mock_fetch:
62 with patch('dulwich.client.LocalGitClient.fetch') as mock_fetch:
63 mock_fetch.side_effect = side_effect
63 mock_fetch.side_effect = side_effect
64 self.remote_git.pull(wire=None, url='/tmp/', apply_refs=False)
64 self.remote_git.pull(wire={}, url='/tmp/', apply_refs=False)
65 determine_wants = self.mock_repo.object_store.determine_wants_all
65 determine_wants = self.mock_repo.object_store.determine_wants_all
66 determine_wants.assert_called_once_with(SAMPLE_REFS)
66 determine_wants.assert_called_once_with(SAMPLE_REFS)
67
67
@@ -79,7 +79,7 b' class TestGitFetch(object):'
79 with patch('dulwich.client.LocalGitClient.fetch') as mock_fetch:
79 with patch('dulwich.client.LocalGitClient.fetch') as mock_fetch:
80 mock_fetch.side_effect = side_effect
80 mock_fetch.side_effect = side_effect
81 self.remote_git.pull(
81 self.remote_git.pull(
82 wire=None, url='/tmp/', apply_refs=False,
82 wire={}, url='/tmp/', apply_refs=False,
83 refs=selected_refs.keys())
83 refs=selected_refs.keys())
84 determine_wants = self.mock_repo.object_store.determine_wants_all
84 determine_wants = self.mock_repo.object_store.determine_wants_all
85 assert determine_wants.call_count == 0
85 assert determine_wants.call_count == 0
@@ -95,18 +95,13 b' class TestGitFetch(object):'
95
95
96 with patch('vcsserver.git.Repo', create=False) as mock_repo:
96 with patch('vcsserver.git.Repo', create=False) as mock_repo:
97 mock_repo().get_refs.return_value = sample_refs
97 mock_repo().get_refs.return_value = sample_refs
98 remote_refs = remote_git.get_remote_refs(wire=None, url=url)
98 remote_refs = remote_git.get_remote_refs(wire={}, url=url)
99 mock_repo().get_refs.assert_called_once_with()
99 mock_repo().get_refs.assert_called_once_with()
100 assert remote_refs == sample_refs
100 assert remote_refs == sample_refs
101
101
102 def test_remove_ref(self):
103 ref_to_remove = 'refs/tags/v0.1.9'
104 self.mock_repo.refs = SAMPLE_REFS.copy()
105 self.remote_git.remove_ref(None, ref_to_remove)
106 assert ref_to_remove not in self.mock_repo.refs
107
108
102
109 class TestReraiseSafeExceptions(object):
103 class TestReraiseSafeExceptions(object):
104
110 def test_method_decorated_with_reraise_safe_exceptions(self):
105 def test_method_decorated_with_reraise_safe_exceptions(self):
111 factory = Mock()
106 factory = Mock()
112 git_remote = git.GitRemote(factory)
107 git_remote = git.GitRemote(factory)
@@ -26,36 +26,17 b' from mock import Mock, MagicMock, patch'
26 from vcsserver import exceptions, hg, hgcompat
26 from vcsserver import exceptions, hg, hgcompat
27
27
28
28
29 class TestHGLookup(object):
30 def setup(self):
31 self.mock_repo = MagicMock()
32 self.mock_repo.__getitem__.side_effect = LookupError(
33 'revision_or_commit_id', 'index', 'message')
34 factory = Mock()
35 factory.repo = Mock(return_value=self.mock_repo)
36 self.remote_hg = hg.HgRemote(factory)
37
38 def test_fail_lookup_hg(self):
39 with pytest.raises(Exception) as exc_info:
40 self.remote_hg.lookup(
41 wire=None, revision='revision_or_commit_id', both=True)
42
43 assert exc_info.value._vcs_kind == 'lookup'
44 assert 'revision_or_commit_id' in exc_info.value.args
45
46
47 class TestDiff(object):
29 class TestDiff(object):
48 def test_raising_safe_exception_when_lookup_failed(self):
30 def test_raising_safe_exception_when_lookup_failed(self):
49 repo = Mock()
31
50 factory = Mock()
32 factory = Mock()
51 factory.repo = Mock(return_value=repo)
52 hg_remote = hg.HgRemote(factory)
33 hg_remote = hg.HgRemote(factory)
53 with patch('mercurial.patch.diff') as diff_mock:
34 with patch('mercurial.patch.diff') as diff_mock:
54 diff_mock.side_effect = LookupError(
35 diff_mock.side_effect = LookupError(
55 'deadbeef', 'index', 'message')
36 'deadbeef', 'index', 'message')
56 with pytest.raises(Exception) as exc_info:
37 with pytest.raises(Exception) as exc_info:
57 hg_remote.diff(
38 hg_remote.diff(
58 wire=None, rev1='deadbeef', rev2='deadbee1',
39 wire={}, commit_id_1='deadbeef', commit_id_2='deadbee1',
59 file_filter=None, opt_git=True, opt_ignorews=True,
40 file_filter=None, opt_git=True, opt_ignorews=True,
60 context=3)
41 context=3)
61 assert type(exc_info.value) == Exception
42 assert type(exc_info.value) == Exception
@@ -45,8 +45,10 b" INVALID_CERTIFICATE_STDERR = '\\n'.join(["
45 reason="SVN not packaged for Cygwin")
45 reason="SVN not packaged for Cygwin")
46 def test_import_remote_repository_certificate_error(stderr, expected_reason):
46 def test_import_remote_repository_certificate_error(stderr, expected_reason):
47 from vcsserver import svn
47 from vcsserver import svn
48 factory = mock.Mock()
49 factory.repo = mock.Mock(return_value=mock.Mock())
48
50
49 remote = svn.SvnRemote(None)
51 remote = svn.SvnRemote(factory)
50 remote.is_path_valid_repository = lambda wire, path: True
52 remote.is_path_valid_repository = lambda wire, path: True
51
53
52 with mock.patch('subprocess.Popen',
54 with mock.patch('subprocess.Popen',
@@ -76,7 +78,10 b' def test_svn_libraries_can_be_imported()'
76 def test_username_password_extraction_from_url(example_url, parts):
78 def test_username_password_extraction_from_url(example_url, parts):
77 from vcsserver import svn
79 from vcsserver import svn
78
80
79 remote = svn.SvnRemote(None)
81 factory = mock.Mock()
82 factory.repo = mock.Mock(return_value=mock.Mock())
83
84 remote = svn.SvnRemote(factory)
80 remote.is_path_valid_repository = lambda wire, path: True
85 remote.is_path_valid_repository = lambda wire, path: True
81
86
82 assert remote.get_url_and_credentials(example_url) == parts
87 assert remote.get_url_and_credentials(example_url) == parts
@@ -15,12 +15,10 b''
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18
19
20 import time
18 import time
21 import logging
19 import logging
22
20
23
21 import vcsserver
24 from vcsserver.utils import safe_str
22 from vcsserver.utils import safe_str
25
23
26
24
@@ -32,6 +30,10 b' def get_access_path(request):'
32 return environ.get('PATH_INFO')
30 return environ.get('PATH_INFO')
33
31
34
32
33 def get_user_agent(environ):
34 return environ.get('HTTP_USER_AGENT')
35
36
35 class RequestWrapperTween(object):
37 class RequestWrapperTween(object):
36 def __init__(self, handler, registry):
38 def __init__(self, handler, registry):
37 self.handler = handler
39 self.handler = handler
@@ -45,14 +47,18 b' class RequestWrapperTween(object):'
45 response = self.handler(request)
47 response = self.handler(request)
46 finally:
48 finally:
47 end = time.time()
49 end = time.time()
48
50 total = end - start
49 log.info('IP: %s Request to path: `%s` time: %.3fs',
51 count = request.request_count()
50 '127.0.0.1', safe_str(get_access_path(request)), end - start)
52 _ver_ = vcsserver.__version__
53 log.info(
54 'Req[%4s] IP: %s %s Request to %s time: %.4fs [%s], VCSServer %s',
55 count, '127.0.0.1', request.environ.get('REQUEST_METHOD'),
56 safe_str(get_access_path(request)), total, get_user_agent(request.environ), _ver_)
51
57
52 return response
58 return response
53
59
54
60
55 def includeme(config):
61 def includeme(config):
56 config.add_tween(
62 config.add_tween(
57 'vcsserver.tweens.RequestWrapperTween',
63 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
58 )
64 )
General Comments 0
You need to be logged in to leave comments. Login now