[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: [PATCH] proposed v3 source format using .git.tar.gz



Here's an updated patch, full diff from head again, with:

- use git-config --null
- git-config --filename only needs a full path if not run from a git WC
- import the VCS module so it can check if the VCS is available
- fix all commands that spawn a subshell
- delete the reflog

-- 
see shy jo
diff --git a/debian/dpkg-dev.install b/debian/dpkg-dev.install
index 49e3835..ee65dbf 100644
--- a/debian/dpkg-dev.install
+++ b/debian/dpkg-dev.install
@@ -56,3 +56,4 @@ usr/share/man/*/dpkg-shlibdeps.1
 usr/share/man/*/*/dpkg-source.1
 usr/share/man/*/dpkg-source.1
 usr/share/perl5/Dpkg/BuildOptions.pm
+usr/share/perl5/Dpkg/Source
diff --git a/man/dpkg-source.1 b/man/dpkg-source.1
index 9bf9ff3..14c17c3 100644
--- a/man/dpkg-source.1
+++ b/man/dpkg-source.1
@@ -55,6 +55,10 @@ will look for the original source tarfile
 or the original source directory
 .IB directory .orig
 depending on the \fB\-sX\fP arguments.
+
+
+If the source package is being built as a version 3 source package using
+a VCS, no upstream tarball or original source directory is needed.
 .TP
 .BR \-h ", " \-\-help
 Show the usage message and exit.
@@ -109,7 +113,9 @@ This option negates a previously set
 .BR \-i [\fIregexp\fP]
 You may specify a perl regular expression to match files you want
 filtered out of the list of files for the diff. (This list is
-generated by a find command.) \fB\-i\fR by itself enables the option,
+generated by a find command.) (If the source package is being built as a
+version 3 source package using a VCS, this is instead used to
+ignore uncommitted files.) \fB\-i\fR by itself enables the option,
 with a default that will filter out control files and directories of the
 most common revision control systems, backup and swap files and Libtool
 build output directories. There can only be one active regexp, of multiple
@@ -162,6 +168,9 @@ will not overwrite existing tarfiles or directories. If this is
 desired then
 .BR \-sA ", " \-sP ", " \-sK ", " \-sU " and " \-sR
 should be used instead.
+.PP
+If the source package is being built as a version 3 source package using
+a VCS, these options do not make sense, and will be ignored.
 .TP
 .BR \-sk
 Specifies to expect the original source as a tarfile, by default
diff --git a/scripts/Dpkg/Source/VCS/git.pm b/scripts/Dpkg/Source/VCS/git.pm
new file mode 100644
index 0000000..431fab3
--- /dev/null
+++ b/scripts/Dpkg/Source/VCS/git.pm
@@ -0,0 +1,257 @@
+#!/usr/bin/perl
+#
+# git support for dpkg-source
+#
+# Copyright © 2007 Joey Hess <joeyh@debian.org>.
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+package Dpkg::Source::VCS::git;
+
+use strict;
+use warnings;
+use Cwd;
+use File::Find;
+use Dpkg;
+use Dpkg::Gettext;
+
+push (@INC, $dpkglibdir);
+require 'controllib.pl';
+
+# 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;
+		}
+	}
+	main::error(sprintf(_g("This source package can only be unpacked using git, which is not in the PATH.")));
+}
+
+sub sanity_check {
+	my $srcdir=shift;
+
+	if (! -d "$srcdir/.git") {
+		main::error(sprintf(_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") {
+		main::error(sprintf(_g("git repository %s uses submodules. This is not yet supported."), $srcdir));
+	}
+
+	# Symlinks from .git to outside could cause unpack failures, or
+	# point to files they shouldn't, so check for and don't allow.
+	if (-l "$srcdir/.git") {
+		main::error(sprintf(_g("%s is a symlink"), "$srcdir/.git"));
+	}
+	my $abs_srcdir=Cwd::abs_path($srcdir);
+	find(sub {
+		if (-l $_) {
+			if (Cwd::abs_path(readlink($_)) !~ /^\Q$abs_srcdir\E(\/|$)/) {
+				main::error(sprintf(_g("%s is a symlink to outside %s"), $File::Find::name, $srcdir));
+			}
+		}
+	}, "$srcdir/.git");
+
+	return 1;
+}
+
+# Returns a hash of arrays of git config values.
+sub read_git_config {
+	my $file=shift;
+
+	my %ret;
+	open(GIT_CONFIG, '-|', "git-config", "--file", $file, "--null", "-l") ||
+		main::subprocerr("git-config");
+	my ($key, $value);
+	while (<GIT_CONFIG>) {
+		if (! defined $key) {
+			$key=$_;
+			chomp $key;
+			$value="";
+		}
+		elsif (/(.*)\0(.*)/) {
+			$value.=$1;
+			push @{$ret{$key}}, $value;
+			$key=$2;
+			chomp $key;
+			$value="";
+		}
+		else {
+			$value.=$1;
+		}
+	}
+	if (defined $key && length $key) {
+		push @{$ret{$key}}, $value;
+	}
+	close(GIT_CONFIG) || main::syserr("git-config exited nonzero");
+
+	return \%ret;
+}
+
+# Called before a tarball is created, to prepare the tar directory.
+sub prep_tar {
+	my $srcdir=shift;
+	my $tardir=shift;
+	
+	sanity_check($srcdir);
+	
+	my $old_cwd=getcwd();
+	chdir($srcdir) ||
+		main::syserr(sprintf(_g("unable to chdir to `%s'"), $srcdir));
+
+	# 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",
+	                                         "--others", @ignores) ||
+		main::subprocerr("git-ls-files");
+	my @files;
+	while (<GIT_LS_FILES>) {
+		chomp;
+		if (! length $main::diff_ignore_regexp ||
+		    ! m/$main::diff_ignore_regexp/o) {
+			push @files, $_;
+		}
+	}
+	close(GIT_LS_FILES) || main::syserr("git-ls-files exited nonzero");
+	if (@files) {
+		main::error(sprintf(_g("uncommitted, not-ignored changes in working directory: %s"),
+		            join(" ", @files)));
+	}
+
+	# git-clone isn't used to copy the repo because the it might be an
+	# unclonable shallow copy.
+	chdir($old_cwd) ||
+		main::syserr(sprintf(_g("unable to chdir to `%s'"), $old_cwd));
+	mkdir($tardir,0755) ||
+		main::syserr(sprintf(_g("unable to create `%s'"), $tardir));
+	system("cp", "-a", "$srcdir/.git", $tardir);
+	$? && main::subprocerr("cp -a $srcdir/.git $tardir");
+	chdir($tardir) ||
+		main::syserr(sprintf(_g("unable to chdir to `%s'"), $tardir));
+	
+	# TODO support for creating a shallow clone for those cases where
+	# uploading the whole repo history is not desired
+
+	# Clean up the new repo to save space.
+	# First, delete the whole reflog, which is not needed in a
+	# distributed source package.
+	system("rm", "-rf", ".git/logs");
+	$? && main::subprocerr("rm -rf .git/logs");
+	system("git-gc", "--prune");
+	$? && main::subprocerr("git-gc --prune");
+
+	# As an optimisation, remove the index. It will be recreated by git
+	# reset during unpack. It's probably small, but you never know, this
+	# might save a lot of space.
+	unlink(".git/index"); # error intentionally ignored
+	
+	chdir($old_cwd) ||
+		main::syserr(sprintf(_g("unable to chdir to `%s'"), $old_cwd));
+
+	return 1;
+}
+
+# Called after a tarball is unpacked, to check out the working copy.
+sub post_unpack_tar {
+	my $srcdir=shift;
+	
+	sanity_check($srcdir);
+
+	my $old_cwd=getcwd();
+	chdir($srcdir) ||
+		main::syserr(sprintf(_g("unable to chdir to `%s'"), $srcdir));
+
+	# Disable git hooks, as unpacking a source package should not
+	# involve running code.
+	foreach my $hook (glob("./.git/hooks/*")) {
+		if (-x $hook) {
+			main::warning(sprintf(_g("executable bit set on %s; clearing"), $hook));
+			chmod(0666 &~ umask(), $hook) ||
+				main::syserr(sprintf(_g("unable to change permission of `%s'"), $hook));
+		}
+	}
+	
+	# This is a paranoia measure, since the index is not normally
+	# provided by possibly-untrusted third parties, remove it if
+	# present (git will recreate it as needed).
+	if (-e ".git/index" || -l ".git/index") {
+		unlink(".git/index") ||
+			main::syserr(sprintf(_g("unable to remove `%s'"), ".git/index"));
+	}
+
+	# Comment out potentially probamatic or annoying stuff in
+	# .git/config.
+	my $safe_fields=qr/^(
+		core\.autocrlf			|
+		branch\..*			|
+		remote\..*			|
+		core\.repositoryformatversion	|
+		core\.filemode			|
+		core\.logallrefupdates		|
+		core\.bare
+		)$/x;
+	my %config=%{read_git_config(".git/config")};
+	foreach my $field (keys %config) {
+		if ($field =~ /$safe_fields/) {
+			delete $config{$field};
+		}
+		else {
+			system("git-config", "--file", ".git/config",
+			       "--unset-all", $field);
+				$? && main::subprocerr("git-config --file .git/config --unset-all $field");
+		}
+	}
+	if (%config) {
+		main::warning(_g("modifying .git/config to comment out some settings"));
+		open(GIT_CONFIG, ">>", ".git/config") ||
+			main::syserr(sprintf(_g("unstable to append to %s", ".git/config")));
+		print GIT_CONFIG "\n# "._g("The following setting(s) were disabled by dpkg-source").":\n";
+		foreach my $field (sort keys %config) {
+			foreach my $value (@{$config{$field}}) {
+				print GIT_CONFIG "# $field=$value\n";
+			}
+		}
+		close GIT_CONFIG;
+	}
+
+	# git-checkout is used to repopulate the WC with files
+	# and recreate the index.
+	system("git-checkout", "-f");
+	$? && main::subprocerr("git-clone -f");
+	
+	chdir($old_cwd) ||
+		main::syserr(sprintf(_g("unable to chdir to `%s'"), $old_cwd));
+
+	return 1;
+}
+
+1
diff --git a/scripts/Makefile.am b/scripts/Makefile.am
index 87ea060..8238bbc 100644
--- a/scripts/Makefile.am
+++ b/scripts/Makefile.am
@@ -55,6 +55,7 @@ CLEANFILES = \
 
 perllibdir = $(PERL_LIBDIR)
 nobase_dist_perllib_DATA = \
+	Dpkg/Source/VCS/git.pm \
 	Dpkg/BuildOptions.pm \
 	Dpkg/Gettext.pm \
 	Dpkg.pm
diff --git a/scripts/dpkg-source.pl b/scripts/dpkg-source.pl
index 5b7802d..bcc3e4f 100755
--- a/scripts/dpkg-source.pl
+++ b/scripts/dpkg-source.pl
@@ -17,7 +17,7 @@ my $controlfile;
 my $changelogfile;
 my $changelogformat;
 
-my $diff_ignore_regexp = '';
+our $diff_ignore_regexp = '';
 my $diff_ignore_default_regexp = '
 # Ignore general backup files
 (?:^|/).*~$|
@@ -71,7 +71,7 @@ $diff_ignore_default_regexp =~ s/\n//sg;
 
 my $sourcestyle = 'X';
 my $min_dscformat = 1;
-my $max_dscformat = 2;
+my $max_dscformat = 3;
 my $def_dscformat = "1.0"; # default format for -b
 
 my $expectprefix;
@@ -190,6 +190,13 @@ sub handleformat {
 	return $1 >= $min_dscformat && $1 <= $max_dscformat;
 }
 
+sub loadvcs {
+	my $vcs = shift;
+	my $mod = "Dpkg::Source::VCS::$vcs";
+	eval qq{require $mod};
+	return $@ || import $mod;
+}
+
 
 my $opmode;
 my $tar_ignore_default_pattern_done;
@@ -252,10 +259,6 @@ $SIG{'PIPE'} = 'DEFAULT';
 
 if ($opmode eq 'build') {
 
-    $sourcestyle =~ y/X/A/;
-    $sourcestyle =~ m/[akpursnAKPUR]/ ||
-        &usageerr(sprintf(_g("source handling style -s%s not allowed with -b"), $sourcestyle));
-
     @ARGV || &usageerr(_g("-b needs a directory"));
     @ARGV<=2 || &usageerr(_g("-b takes at most a directory and an orig source argument"));
     my $dir = shift(@ARGV);
@@ -283,7 +286,7 @@ if ($opmode eq 'build') {
         if (s/^C //) {
 	    if (m/^Source$/i) {
 		setsourcepackage($v);
-	    } elsif (m/^(Standards-Version|Origin|Maintainer|Homepage)$/i ||
+	    } elsif (m/^(Format|Standards-Version|Origin|Maintainer|Homepage)$/i ||
 	             m/^Vcs-(Browser|Arch|Bzr|Cvs|Darcs|Git|Hg|Mtn|Svn)$/i) {
 		$f{$_}= $v;
 	    }
@@ -351,6 +354,39 @@ if ($opmode eq 'build') {
             &internerr(sprintf(_g("value from nowhere, with key >%s< and value >%s<"), $_, $v));
         }
     }
+    
+    my $vcs;
+    if ($f{Format} =~ /^\s*(\d+\.\d+)\s*$/) {
+	    if ($1 >= 3.0) {
+	        error(sprintf(_g("don't know how to generate %s format source package (missing vcs specifier in Format field?)"), $1));
+	    }
+	    if ($1 > 1.0) {
+	        error(sprintf(_g("don't know how to generate %s format source package"), $1));
+	    }
+    }
+    elsif ($f{Format} =~ /^\s*(\d+(?:\.\d+)?)\s+\((\w+)\)\s*$/) {
+	    $f{Format}=$1;
+	    if ($1 < 3.0) {
+	        error(sprintf(_g("control info file 'Format' field for version %s does not support vcs specifier \"%s\""), $1, $2));
+	    }
+            if ($1 >= 4) {
+	        error(sprintf(_g("unsupported control info file 'Format' value \"%s\""), $1));
+            }
+
+	    $vcs=$2;
+	    loadvcs($2)
+	    	|| error(sprintf(_g("unsupported vcs \"%s\" in control info file 'Format' field"), $2));
+            
+	    if ($sourcestyle =~ /[akpursKPUR]/) {
+		warning(sprintf(_g("source handling style -s%s not supported when generating %s format source package"), $sourcestyle, $vcs));
+            }
+            $sourcestyle='v';
+    }
+    
+    $sourcestyle =~ y/X/A/;
+    $sourcestyle =~ m/[akpursnAKPURv]/ ||
+        &usageerr(sprintf(_g("source handling style -s%s not allowed with -b"), $sourcestyle));
+
 
     $f{'Binary'}= join(', ',@binarypackages);
     for my $f (keys %override) {
@@ -438,7 +474,17 @@ if ($opmode eq 'build') {
     my $tardirbase;
     my $origdirname;
 
-    if ($sourcestyle ne 'n') {
+    if ($sourcestyle eq 'v') {
+	$tarname="$basenamerev.$vcs.tar.gz";
+        $tardirbase= $dirbase; $tardirname= "$tarname.tmp";
+
+	eval qq{Dpkg::Source::VCS::${vcs}::prep_tar(\$dir, \$tardirname)};
+	if ($@) {
+            &syserr($@);
+	}
+	push @exit_handlers, sub { erasedir($tardirname) };
+    }
+    elsif ($sourcestyle ne 'n') {
 	my $origdirbase = $origdir;
 	$origdirbase =~ s,/?$,,;
         $origdirbase =~ s,[^/]+$,,; $origdirname= $&;
@@ -458,10 +504,10 @@ if ($opmode eq 'build') {
         $tarname= "$basenamerev.tar.gz";
     }
 
-    if ($sourcestyle =~ m/[nurUR]/) {
+    if ($sourcestyle =~ m/[nurURv]/) {
 
         if (stat($tarname)) {
-            $sourcestyle =~ m/[nUR]/ ||
+            $sourcestyle =~ m/[nURv]/ ||
                 &error(sprintf(_g("tarfile `%s' already exists, not overwriting,".
                        " giving up; use -sU or -sR to override"), $tarname));
         } elsif ($! != ENOENT) {
@@ -530,6 +576,10 @@ if ($opmode eq 'build') {
             &syserr(sprintf(_g("unable to remove `%s'"), "$origtargz.tmp-nest"));
 	    pop @exit_handlers;
     }
+
+    if ($sourcestyle eq 'v') {
+        erasedir($tardirname)
+    }
         
     if ($sourcestyle =~ m/[kpursKPUR]/) {
         
@@ -790,6 +840,7 @@ if ($opmode eq 'build') {
     my @tarfiles;
     my $difffile;
     my $debianfile;
+    my %vcsfiles;
     my %seen;
     for my $file (split(/\n /, $files)) {
         next if $file eq '';
@@ -813,6 +864,11 @@ if ($opmode eq 'build') {
 	    else    { unshift @tarfiles, $file; }
 	} elsif (/^\.debian\.tar$/) {
 	    $debianfile = $file;
+	} elsif (/^\.(\w+)\.tar$/) {
+            my $vcs=$1;
+            # TODO try to load vcs module
+            push @tarfiles, $file;
+            $vcsfiles{$file}=$vcs;
 	} elsif (/^\.diff$/) {
 	    $difffile = $file;
 	} else {
@@ -825,14 +881,17 @@ if ($opmode eq 'build') {
     if ($native) {
 	warning(_g("multiple tarfiles in native package")) if @tarfiles > 1;
 	warning(_g("native package with .orig.tar"))
-	    unless $seen{'.tar'} or $seen{"-$revision.tar"};
+	    unless $seen{'.tar'} or $seen{"-$revision.tar"} or %vcsfiles;
     } else {
-	warning(_g("no upstream tarfile in Files field")) unless $seen{'.orig.tar'};
+	warning(_g("no upstream tarfile in Files field")) unless $seen{'.orig.tar'} or %vcsfiles;
 	if ($dscformat =~ /^1\./) {
 	    warning(sprintf(_g("multiple upstream tarballs in %s format dsc"), $dscformat)) if @tarfiles > 1;
 	    warning(sprintf(_g("debian.tar in %s format dsc"), $dscformat)) if $debianfile;
 	}
     }
+    if (%vcsfiles && $dscformat !~ /^3\./) {
+	warning(sprintf(_g("<rc>.tar file in %s format dsc"), $dscformat));
+    }
 
     $newdirectory = $sourcepackage.'-'.$baseversion unless defined($newdirectory);
     $expectprefix = $newdirectory;
@@ -908,6 +967,16 @@ if ($opmode eq 'build') {
 		$? && subprocerr("cp $expectprefix to $newdirectory.tmp-keep");
 	    }
 	}
+
+        if (exists $vcsfiles{$tarfile}) {
+	    printf(_g("%s: extracting source from %s repository")."\n", $progname, $vcsfiles{$tarfile});
+	    loadvcs($vcsfiles{$tarfile})
+	    	|| error(sprintf(_g("unsupported vcs \"%s\""), $vcsfiles{$tarfile}));
+	    eval qq{Dpkg::Source::VCS::$vcsfiles{$tarfile}::post_unpack_tar(\$target)};
+	    if ($@) {
+                &syserr($@);
+	    }
+        }
     }
 
     my @patches;

Attachment: signature.asc
Description: Digital signature


Reply to: