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