# # git support for dpkg-source # # Copyright © 2007,2010 Joey Hess . # Copyright © 2008 Frank Lichtenheld # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . package Dpkg::Source::Package::V3::git; use strict; use warnings; our $VERSION = "0.02"; use base 'Dpkg::Source::Package'; use Cwd qw(abs_path getcwd); use File::Basename; use File::Temp qw(tempdir); use Dpkg; use Dpkg::Gettext; use Dpkg::ErrorHandling; use Dpkg::Exit; use Dpkg::Source::Functions qw(erasedir); our $CURRENT_MINOR_VERSION = "0"; # Remove variables from the environment that might cause git to do # something unexpected. delete $ENV{GIT_DIR}; delete $ENV{GIT_INDEX_FILE}; delete $ENV{GIT_OBJECT_DIRECTORY}; delete $ENV{GIT_ALTERNATE_OBJECT_DIRECTORIES}; delete $ENV{GIT_WORK_TREE}; sub import { foreach my $dir (split(/:/, $ENV{PATH})) { if (-x "$dir/git") { return 1; } } error(_g("This source package can only be manipulated using git, " . "which is not in the PATH.")); } sub sanity_check { my $srcdir = shift; if (! -d "$srcdir/.git") { error(_g("source directory is not the top directory of a git " . "repository (%s/.git not present), but Format git was " . "specified"), $srcdir); } if (-s "$srcdir/.gitmodules") { error(_g("git repository %s uses submodules. This is not yet supported."), $srcdir); } return 1; } sub parse_cmdline_option { my ($self, $opt) = @_; return 1 if $self->SUPER::parse_cmdline_option($opt); if ($opt =~ /^--git-ref=(.*)$/) { push @{$self->{'options'}{'git-ref'}}, $1; return 1; } elsif ($opt =~ /^--git-depth=(\d+)$/) { $self->{'options'}{'git-depth'} = $1; return 1; } return 0; } sub can_build { my ($self, $dir) = @_; return (-d "$dir/.git", _g("doesn't contain a git repository")); } sub do_build { my ($self, $dir) = @_; my $diff_ignore_regexp = $self->{'options'}{'diff_ignore_regexp'}; $dir =~ s{/+$}{}; # Strip trailing / my ($dirname, $updir) = fileparse($dir); my $basenamerev = $self->get_basename(1); sanity_check($dir); my $old_cwd = getcwd(); chdir($dir) || syserr(_g("unable to chdir to `%s'"), $dir); # Check for uncommitted files. # To support dpkg-source -i, get a list of files # equivalent to the ones git status finds, and remove any # ignored files from it. my @ignores = "--exclude-per-directory=.gitignore"; my $core_excludesfile = `git config --get core.excludesfile`; chomp $core_excludesfile; if (length $core_excludesfile && -e $core_excludesfile) { push @ignores, "--exclude-from=$core_excludesfile"; } if (-e ".git/info/exclude") { push @ignores, "--exclude-from=.git/info/exclude"; } open(GIT_LS_FILES, '-|', "git", "ls-files", "--modified", "--deleted", "-z", "--others", @ignores) || subprocerr("git ls-files"); my @files; { local $/ = "\0"; while () { chomp; if (! length $diff_ignore_regexp || ! m/$diff_ignore_regexp/o) { push @files, $_; } } } close(GIT_LS_FILES) || syserr(_g("git ls-files exited nonzero")); if (@files) { error(_g("uncommitted, not-ignored changes in working directory: %s"), join(" ", @files)); } # If a depth was specified, need to create a shallow clone and # bundle that. my $tmp; my $shallowfile; if ($self->{'options'}{'git-depth'}) { chdir($old_cwd) || syserr(_g("unable to chdir to `%s'"), $old_cwd); $tmp = tempdir("$dirname.git.XXXXXX", DIR => $updir); push @Dpkg::Exit::handlers, sub { erasedir($tmp) }; my $clone_dir = "$tmp/repo.git"; # file:// is needed to avoid local cloning, which does not # create a shallow clone. info(_g("creating shallow clone with depth %s"), $self->{'options'}{'git-depth'}); system("git", "clone", "--depth=".$self->{'options'}{'git-depth'}, "--quiet", "--bare", "file://" . abs_path($dir), $clone_dir); $? && subprocerr("git clone"); chdir($clone_dir) || syserr(_g("unable to chdir to `%s'"), $clone_dir); $shallowfile = "$basenamerev.gitshallow"; system("cp", "-f", "shallow", "$old_cwd/$shallowfile"); $? && subprocerr("cp shallow"); } # Create the git bundle. my $bundlefile = "$basenamerev.git"; my @bundle_arg=$self->{'options'}{'git-ref'} ? (@{$self->{'options'}{'git-ref'}}) : "--all"; info(_g("bundling: %s"), join(" ", @bundle_arg)); system("git", "bundle", "create", "$old_cwd/$bundlefile", @bundle_arg, "HEAD", # ensure HEAD is included no matter what "--", # avoids ambiguity error when referring to eg, a debian branch ); $? && subprocerr("git bundle"); chdir($old_cwd) || syserr(_g("unable to chdir to `%s'"), $old_cwd); if (defined $tmp) { erasedir($tmp); pop @Dpkg::Exit::handlers; } $self->add_file($bundlefile); if (defined $shallowfile) { $self->add_file($shallowfile); } } sub do_extract { my ($self, $newdirectory) = @_; my $fields = $self->{'fields'}; my $dscdir = $self->{'basedir'}; my $basenamerev = $self->get_basename(1); my @files = $self->get_files(); my ($bundle, $shallow); foreach my $file (@files) { if ($file =~ /^\Q$basenamerev\E\.git$/) { if (! defined $bundle) { $bundle = $file; } else { error(_g("format v3.0 (git) uses only one .git file")); } } elsif ($file =~ /^\Q$basenamerev\E\.gitshallow$/) { if (! defined $shallow) { $shallow = $file; } else { error(_g("format v3.0 (git) uses only one .gitshallow file")); } } else { error(_g("format v3.0 (git) unknown file: %s", $file)); } } if (! defined $bundle) { error(_g("format v3.0 (git) expected %s"), "$basenamerev.git"); } erasedir($newdirectory); # Extract git bundle. info(_g("cloning %s"), $bundle); system("git", "clone", "--quiet", $dscdir.$bundle, $newdirectory); $? && subprocerr("git bundle"); if (defined $shallow) { # Move shallow info file into place, so git does not # try to follow parents of shallow refs. info(_g("setting up shallow clone")); system("cp", "-f", $dscdir.$shallow, "$newdirectory/.git/shallow"); $? && subprocerr("cp"); } sanity_check($newdirectory); } 1;