git.pm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. #!/usr/bin/perl
  2. #
  3. # git support for dpkg-source
  4. #
  5. # Copyright © 2007 Joey Hess <joeyh@debian.org>.
  6. # Copyright © 2008 Frank Lichtenheld <djpig@debian.org>
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program; if not, write to the Free Software
  20. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  21. package Dpkg::Source::Package::V3::git;
  22. use strict;
  23. use warnings;
  24. use base 'Dpkg::Source::Package';
  25. use Cwd;
  26. use File::Basename;
  27. use File::Find;
  28. use File::Temp qw(tempdir);
  29. use Dpkg;
  30. use Dpkg::Gettext;
  31. use Dpkg::Compression;
  32. use Dpkg::ErrorHandling;
  33. use Dpkg::Source::Archive;
  34. use Dpkg::Exit;
  35. use Dpkg::Source::Functions qw(erasedir);
  36. our $CURRENT_MINOR_VERSION = "0";
  37. # Remove variables from the environment that might cause git to do
  38. # something unexpected.
  39. delete $ENV{GIT_DIR};
  40. delete $ENV{GIT_INDEX_FILE};
  41. delete $ENV{GIT_OBJECT_DIRECTORY};
  42. delete $ENV{GIT_ALTERNATE_OBJECT_DIRECTORIES};
  43. delete $ENV{GIT_WORK_TREE};
  44. sub import {
  45. foreach my $dir (split(/:/, $ENV{PATH})) {
  46. if (-x "$dir/git") {
  47. return 1;
  48. }
  49. }
  50. error(_g("This source package can only be manipulated using git, which is not in the PATH."));
  51. }
  52. sub sanity_check {
  53. my $srcdir = shift;
  54. if (! -d "$srcdir/.git") {
  55. error(_g("source directory is not the top directory of a git repository (%s/.git not present), but Format git was specified"),
  56. $srcdir);
  57. }
  58. if (-s "$srcdir/.gitmodules") {
  59. error(_g("git repository %s uses submodules. This is not yet supported."),
  60. $srcdir);
  61. }
  62. # Symlinks from .git to outside could cause unpack failures, or
  63. # point to files they shouldn't, so check for and don't allow.
  64. if (-l "$srcdir/.git") {
  65. error(_g("%s is a symlink"), "$srcdir/.git");
  66. }
  67. my $abs_srcdir = Cwd::abs_path($srcdir);
  68. find(sub {
  69. if (-l $_) {
  70. if (Cwd::abs_path(readlink($_)) !~ /^\Q$abs_srcdir\E(\/|$)/) {
  71. error(_g("%s is a symlink to outside %s"),
  72. $File::Find::name, $srcdir);
  73. }
  74. }
  75. }, "$srcdir/.git");
  76. return 1;
  77. }
  78. # Returns a hash of arrays of git config values.
  79. sub read_git_config {
  80. my $file = shift;
  81. my %ret;
  82. open(GIT_CONFIG, '-|', "git", "config", "--file", $file, "--null", "-l") ||
  83. subprocerr("git config");
  84. local $/ = "\0";
  85. while (<GIT_CONFIG>) {
  86. chomp;
  87. my ($key, $value) = split(/\n/, $_, 2);
  88. push @{$ret{$key}}, $value;
  89. }
  90. close(GIT_CONFIG) || syserr(_g("git config exited nonzero"));
  91. return \%ret;
  92. }
  93. sub can_build {
  94. my ($self, $dir) = @_;
  95. return (-d "$dir/.git", _g("doesn't contain a git repository"));
  96. }
  97. sub do_build {
  98. my ($self, $dir) = @_;
  99. my @argv = @{$self->{'options'}{'ARGV'}};
  100. #TODO: warn here?
  101. # my @tar_ignore = map { "--exclude=$_" } @{$self->{'options'}{'tar_ignore'}};
  102. my $diff_ignore_regexp = $self->{'options'}{'diff_ignore_regexp'};
  103. $dir =~ s{/+$}{}; # Strip trailing /
  104. my ($dirname, $updir) = fileparse($dir);
  105. if (scalar(@argv)) {
  106. usageerr(_g("-b takes only one parameter with format `%s'"),
  107. $self->{'fields'}{'Format'});
  108. }
  109. my $sourcepackage = $self->{'fields'}{'Source'};
  110. my $basenamerev = $self->get_basename(1);
  111. my $basename = $self->get_basename();
  112. my $basedirname = $basename;
  113. $basedirname =~ s/_/-/;
  114. sanity_check($dir);
  115. my $old_cwd = getcwd();
  116. chdir($dir) ||
  117. syserr(_g("unable to chdir to `%s'"), $dir);
  118. # Check for uncommitted files.
  119. # To support dpkg-source -i, get a list of files
  120. # equivalent to the ones git status finds, and remove any
  121. # ignored files from it.
  122. my @ignores = "--exclude-per-directory=.gitignore";
  123. my $core_excludesfile = `git config --get core.excludesfile`;
  124. chomp $core_excludesfile;
  125. if (length $core_excludesfile && -e $core_excludesfile) {
  126. push @ignores, "--exclude-from=$core_excludesfile";
  127. }
  128. if (-e ".git/info/exclude") {
  129. push @ignores, "--exclude-from=.git/info/exclude";
  130. }
  131. open(GIT_LS_FILES, '-|', "git", "ls-files", "--modified", "--deleted",
  132. "-z", "--others", @ignores) ||
  133. subprocerr("git ls-files");
  134. my @files;
  135. { local $/ = "\0";
  136. while (<GIT_LS_FILES>) {
  137. chomp;
  138. if (! length $diff_ignore_regexp ||
  139. ! m/$diff_ignore_regexp/o) {
  140. push @files, $_;
  141. }
  142. }
  143. }
  144. close(GIT_LS_FILES) || syserr(_g("git ls-files exited nonzero"));
  145. if (@files) {
  146. error(_g("uncommitted, not-ignored changes in working directory: %s"),
  147. join(" ", @files));
  148. }
  149. # git clone isn't used to copy the repo because the it might be an
  150. # unclonable shallow copy.
  151. chdir($old_cwd) ||
  152. syserr(_g("unable to chdir to `%s'"), $old_cwd);
  153. my $tmp = tempdir("$dirname.git.XXXXXX", DIR => $updir);
  154. push @Dpkg::Exit::handlers, sub { erasedir($tmp) };
  155. my $tardir = "$tmp/$dirname";
  156. mkdir($tardir) ||
  157. syserr(_g("cannot create directory %s"), $tardir);
  158. system("cp", "-a", "$dir/.git", $tardir);
  159. $? && subprocerr("cp -a $dir/.git $tardir");
  160. chdir($tardir) ||
  161. syserr(_g("unable to chdir to `%s'"), $tardir);
  162. # TODO support for creating a shallow clone for those cases where
  163. # uploading the whole repo history is not desired
  164. # Clean up the new repo to save space.
  165. # First, delete the whole reflog, which is not needed in a
  166. # distributed source package.
  167. system("rm", "-rf", ".git/logs");
  168. $? && subprocerr("rm -rf .git/logs");
  169. system("git", "gc", "--prune");
  170. $? && subprocerr("git gc --prune");
  171. # .git/gitweb is created and used by git instaweb and should not be
  172. # transferwed by source package.
  173. system("rm", "-rf", ".git/gitweb");
  174. $? && subprocerr("rm -rf .git/gitweb");
  175. # As an optimisation, remove the index. It will be recreated by git
  176. # reset during unpack. It's probably small, but you never know, this
  177. # might save a lot of space. (Also, the index file may not be
  178. # portable.)
  179. unlink(".git/index"); # error intentionally ignored
  180. chdir($old_cwd) ||
  181. syserr(_g("unable to chdir to `%s'"), $old_cwd);
  182. # Create the tar file
  183. my $debianfile = "$basenamerev.git.tar." . $self->{'options'}{'comp_ext'};
  184. info(_g("building %s in %s"),
  185. $sourcepackage, $debianfile);
  186. my $tar = Dpkg::Source::Archive->new(filename => $debianfile,
  187. compression => $self->{'options'}{'compression'},
  188. compression_level => $self->{'options'}{'comp_level'});
  189. $tar->create('chdir' => $tmp);
  190. $tar->add_directory($dirname);
  191. $tar->finish();
  192. erasedir($tmp);
  193. pop @Dpkg::Exit::handlers;
  194. $self->add_file($debianfile);
  195. }
  196. # Called after a tarball is unpacked, to check out the working copy.
  197. sub do_extract {
  198. my ($self, $newdirectory) = @_;
  199. my $fields = $self->{'fields'};
  200. my $dscdir = $self->{'basedir'};
  201. my $basename = $self->get_basename();
  202. my $basenamerev = $self->get_basename(1);
  203. my @files = $self->get_files();
  204. if (@files > 1) {
  205. error(_g("format v3.0 uses only one source file"));
  206. }
  207. my $tarfile = $files[0];
  208. if ($tarfile !~ /^\Q$basenamerev\E\.git\.tar\.$comp_regex$/) {
  209. error(_g("expected %s, got %s"),
  210. "$basenamerev.git.tar.$comp_regex", $tarfile);
  211. }
  212. erasedir($newdirectory);
  213. # Extract main tarball
  214. info(_g("unpacking %s"), $tarfile);
  215. my $tar = Dpkg::Source::Archive->new(filename => "$dscdir$tarfile");
  216. $tar->extract($newdirectory);
  217. sanity_check($newdirectory);
  218. my $old_cwd = getcwd();
  219. chdir($newdirectory) ||
  220. syserr(_g("unable to chdir to `%s'"), $newdirectory);
  221. # Disable git hooks, as unpacking a source package should not
  222. # involve running code.
  223. foreach my $hook (glob("./.git/hooks/*")) {
  224. if (-x $hook) {
  225. warning(_g("executable bit set on %s; clearing"), $hook);
  226. chmod(0666 &~ umask(), $hook) ||
  227. syserr(_g("unable to change permission of `%s'"), $hook);
  228. }
  229. }
  230. # This is a paranoia measure, since the index is not normally
  231. # provided by possibly-untrusted third parties, remove it if
  232. # present (git will recreate it as needed).
  233. if (-e ".git/index" || -l ".git/index") {
  234. unlink(".git/index") ||
  235. syserr(_g("unable to remove `%s'"), ".git/index");
  236. }
  237. # Comment out potentially probamatic or annoying stuff in
  238. # .git/config.
  239. my $safe_fields = qr/^(
  240. core\.autocrlf |
  241. branch\..* |
  242. remote\..* |
  243. core\.repositoryformatversion |
  244. core\.filemode |
  245. core\.logallrefupdates |
  246. core\.bare
  247. )$/x;
  248. my %config = %{read_git_config(".git/config")};
  249. foreach my $field (keys %config) {
  250. if ($field =~ /$safe_fields/) {
  251. delete $config{$field};
  252. }
  253. else {
  254. system("git", "config", "--file", ".git/config",
  255. "--unset-all", $field);
  256. $? && subprocerr("git config --file .git/config --unset-all $field");
  257. }
  258. }
  259. if (%config) {
  260. warning(_g("modifying .git/config to comment out some settings"));
  261. open(GIT_CONFIG, ">>", ".git/config") ||
  262. syserr(_g("unable to append to %s"), ".git/config");
  263. print GIT_CONFIG "\n# "._g("The following setting(s) were disabled by dpkg-source").":\n";
  264. foreach my $field (sort keys %config) {
  265. foreach my $value (@{$config{$field}}) {
  266. if (defined($value)) {
  267. print GIT_CONFIG "# $field=$value\n";
  268. } else {
  269. print GIT_CONFIG "# $field\n";
  270. }
  271. }
  272. }
  273. close GIT_CONFIG;
  274. }
  275. # .git/gitweb is created and used by git instaweb and should not be
  276. # transferwed by source package.
  277. system("rm", "-rf", ".git/gitweb");
  278. $? && subprocerr("rm -rf .git/gitweb");
  279. # git checkout is used to repopulate the WC with files
  280. # and recreate the index.
  281. system("git", "checkout", "-f");
  282. $? && subprocerr("git checkout -f");
  283. chdir($old_cwd) ||
  284. syserr(_g("unable to chdir to `%s'"), $old_cwd);
  285. }
  286. 1;