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