git.pm 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  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_0::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 qw(error warning subprocerr syserr);
  33. use Dpkg::Version qw(check_version);
  34. use Dpkg::Source::Archive;
  35. use Dpkg::Exit;
  36. use Dpkg::Source::Functions qw(erasedir);
  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 do_build {
  94. my ($self, $dir) = @_;
  95. my @argv = @{$self->{'options'}{'ARGV'}};
  96. #TODO: warn here?
  97. # my @tar_ignore = map { "--exclude=$_" } @{$self->{'options'}{'tar_ignore'}};
  98. my $diff_ignore_regexp = $self->{'options'}{'diff_ignore_regexp'};
  99. $dir =~ s{/+$}{}; # Strip trailing /
  100. my ($dirname, $updir) = fileparse($dir);
  101. if (scalar(@argv)) {
  102. usageerr(_g("-b takes only one parameter with v3.0 source packages"));
  103. }
  104. my $sourcepackage = $self->{'fields'}{'Source'};
  105. my $basenamerev = $self->get_basename(1);
  106. my $basename = $self->get_basename();
  107. my $basedirname = $basename;
  108. $basedirname =~ s/_/-/;
  109. sanity_check($dir);
  110. my $old_cwd=getcwd();
  111. chdir($dir) ||
  112. syserr(_g("unable to chdir to `%s'"), $dir);
  113. # Check for uncommitted files.
  114. # To support dpkg-source -i, get a list of files
  115. # equivalent to the ones git status finds, and remove any
  116. # ignored files from it.
  117. my @ignores="--exclude-per-directory=.gitignore";
  118. my $core_excludesfile=`git config --get core.excludesfile`;
  119. chomp $core_excludesfile;
  120. if (length $core_excludesfile && -e $core_excludesfile) {
  121. push @ignores, "--exclude-from='$core_excludesfile'";
  122. }
  123. if (-e ".git/info/exclude") {
  124. push @ignores, "--exclude-from=.git/info/exclude";
  125. }
  126. open(GIT_LS_FILES, '-|', "git", "ls-files", "--modified", "--deleted",
  127. "-z", "--others", @ignores) ||
  128. subprocerr("git ls-files");
  129. my @files;
  130. { local $/ = "\0";
  131. while (<GIT_LS_FILES>) {
  132. chomp;
  133. if (! length $diff_ignore_regexp ||
  134. ! m/$diff_ignore_regexp/o) {
  135. push @files, $_;
  136. }
  137. }
  138. }
  139. close(GIT_LS_FILES) || syserr(_g("git ls-files exited nonzero"));
  140. if (@files) {
  141. error(_g("uncommitted, not-ignored changes in working directory: %s"),
  142. join(" ", @files));
  143. }
  144. # git clone isn't used to copy the repo because the it might be an
  145. # unclonable shallow copy.
  146. chdir($old_cwd) ||
  147. syserr(_g("unable to chdir to `%s'"), $old_cwd);
  148. my $tmp = tempdir("$dirname.git.XXXXXX", DIR => $updir);
  149. push @Dpkg::Exit::handlers, sub { erasedir($tmp) };
  150. my $tardir = "$tmp/$dirname";
  151. mkdir($tardir) ||
  152. syserr(_g("cannot create directory %s"), $tardir);
  153. system("cp", "-a", "$dir/.git", $tardir);
  154. $? && subprocerr("cp -a $dir/.git $tardir");
  155. chdir($tardir) ||
  156. syserr(_g("unable to chdir to `%s'"), $tardir);
  157. # TODO support for creating a shallow clone for those cases where
  158. # uploading the whole repo history is not desired
  159. # Clean up the new repo to save space.
  160. # First, delete the whole reflog, which is not needed in a
  161. # distributed source package.
  162. system("rm", "-rf", ".git/logs");
  163. $? && subprocerr("rm -rf .git/logs");
  164. system("git", "gc", "--prune");
  165. $? && subprocerr("git gc --prune");
  166. # .git/gitweb is created and used by git instaweb and should not be
  167. # transferwed by source package.
  168. system("rm", "-rf", ".git/gitweb");
  169. $? && subprocerr("rm -rf .git/gitweb");
  170. # As an optimisation, remove the index. It will be recreated by git
  171. # reset during unpack. It's probably small, but you never know, this
  172. # might save a lot of space. (Also, the index file may not be
  173. # portable.)
  174. unlink(".git/index"); # error intentionally ignored
  175. chdir($old_cwd) ||
  176. syserr(_g("unable to chdir to `%s'"), $old_cwd);
  177. # Create the tar file
  178. my $debianfile = "$basenamerev.git.tar." . $self->{'options'}{'comp_ext'};
  179. printf(_g("%s: building %s in %s")."\n",
  180. $progname, $sourcepackage, $debianfile);
  181. my $tar = Dpkg::Source::Archive->new(filename => $debianfile,
  182. compression => $self->{'options'}{'compression'},
  183. compression_level => $self->{'options'}{'comp_level'});
  184. $tar->create('chdir' => $tmp);
  185. $tar->add_directory($dirname);
  186. $tar->finish();
  187. erasedir($tmp);
  188. pop @Dpkg::Exit::handlers;
  189. $self->add_file($debianfile);
  190. }
  191. # Called after a tarball is unpacked, to check out the working copy.
  192. sub do_extract {
  193. my ($self, $newdirectory) = @_;
  194. my $fields = $self->{'fields'};
  195. my $dscdir = $self->{'basedir'};
  196. check_version($fields->{'Version'});
  197. my $basename = $self->get_basename();
  198. my $basenamerev = $self->get_basename(1);
  199. my @files = $self->get_files();
  200. if (@files > 1) {
  201. error(_g("format v3.0 uses only one source file"));
  202. }
  203. my $tarfile = $files[0];
  204. if ($tarfile !~ /^\Q$basenamerev\E\.git\.tar\.$comp_regex$/) {
  205. error(_g("expected %s, got %s"),
  206. "$basenamerev.git.tar.$comp_regex", $tarfile);
  207. }
  208. erasedir($newdirectory);
  209. # Extract main tarball
  210. printf(_g("%s: unpacking %s")."\n", $progname, $tarfile);
  211. my $tar = Dpkg::Source::Archive->new(filename => "$dscdir$tarfile");
  212. $tar->extract($newdirectory);
  213. sanity_check($newdirectory);
  214. my $old_cwd=getcwd();
  215. chdir($newdirectory) ||
  216. syserr(_g("unable to chdir to `%s'"), $newdirectory);
  217. # Disable git hooks, as unpacking a source package should not
  218. # involve running code.
  219. foreach my $hook (glob("./.git/hooks/*")) {
  220. if (-x $hook) {
  221. warning(_g("executable bit set on %s; clearing"), $hook);
  222. chmod(0666 &~ umask(), $hook) ||
  223. syserr(_g("unable to change permission of `%s'"), $hook);
  224. }
  225. }
  226. # This is a paranoia measure, since the index is not normally
  227. # provided by possibly-untrusted third parties, remove it if
  228. # present (git will recreate it as needed).
  229. if (-e ".git/index" || -l ".git/index") {
  230. unlink(".git/index") ||
  231. syserr(_g("unable to remove `%s'"), ".git/index");
  232. }
  233. # Comment out potentially probamatic or annoying stuff in
  234. # .git/config.
  235. my $safe_fields=qr/^(
  236. core\.autocrlf |
  237. branch\..* |
  238. remote\..* |
  239. core\.repositoryformatversion |
  240. core\.filemode |
  241. core\.logallrefupdates |
  242. core\.bare
  243. )$/x;
  244. my %config=%{read_git_config(".git/config")};
  245. foreach my $field (keys %config) {
  246. if ($field =~ /$safe_fields/) {
  247. delete $config{$field};
  248. }
  249. else {
  250. system("git", "config", "--file", ".git/config",
  251. "--unset-all", $field);
  252. $? && subprocerr("git config --file .git/config --unset-all $field");
  253. }
  254. }
  255. if (%config) {
  256. warning(_g("modifying .git/config to comment out some settings"));
  257. open(GIT_CONFIG, ">>", ".git/config") ||
  258. syserr(_g("unstable to append to %s"), ".git/config");
  259. print GIT_CONFIG "\n# "._g("The following setting(s) were disabled by dpkg-source").":\n";
  260. foreach my $field (sort keys %config) {
  261. foreach my $value (@{$config{$field}}) {
  262. if (defined($value)) {
  263. print GIT_CONFIG "# $field=$value\n";
  264. } else {
  265. print GIT_CONFIG "# $field\n";
  266. }
  267. }
  268. }
  269. close GIT_CONFIG;
  270. }
  271. # .git/gitweb is created and used by git instaweb and should not be
  272. # transferwed by source package.
  273. system("rm", "-rf", ".git/gitweb");
  274. $? && subprocerr("rm -rf .git/gitweb");
  275. # git checkout is used to repopulate the WC with files
  276. # and recreate the index.
  277. system("git", "checkout", "-f");
  278. $? && subprocerr("git checkout -f");
  279. chdir($old_cwd) ||
  280. syserr(_g("unable to chdir to `%s'"), $old_cwd);
  281. }
  282. 1