2 # git support for dpkg-source
4 # Copyright © 2007,2010 Joey Hess <joeyh@debian.org>.
5 # Copyright © 2008 Frank Lichtenheld <djpig@debian.org>
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <https://www.gnu.org/licenses/>.
20 package Dpkg::Source::Package::V3::Git;
25 our $VERSION = '0.02';
27 use Cwd qw(abs_path getcwd);
29 use File::Temp qw(tempdir);
32 use Dpkg::ErrorHandling;
33 use Dpkg::Exit qw(push_exit_handler pop_exit_handler);
34 use Dpkg::Source::Functions qw(erasedir);
36 use parent qw(Dpkg::Source::Package);
38 our $CURRENT_MINOR_VERSION = '0';
40 # Remove variables from the environment that might cause git to do
41 # something unexpected.
43 delete $ENV{GIT_INDEX_FILE};
44 delete $ENV{GIT_OBJECT_DIRECTORY};
45 delete $ENV{GIT_ALTERNATE_OBJECT_DIRECTORIES};
46 delete $ENV{GIT_WORK_TREE};
49 foreach my $dir (split(/:/, $ENV{PATH})) {
54 error(g_('cannot unpack git-format source package because ' .
55 'git is not in the PATH'));
61 if (! -d "$srcdir/.git") {
62 error(g_('source directory is not the top directory of a git ' .
63 'repository (%s/.git not present), but Format git was ' .
64 'specified'), $srcdir);
66 if (-s "$srcdir/.gitmodules") {
67 error(g_('git repository %s uses submodules; this is not yet supported'),
74 my @module_cmdline = (
76 name => '--git-ref=<ref>',
77 help => N_('specify a git <ref> to include in the git bundle'),
80 name => '--git-depth=<number>',
81 help => N_('create a shallow clone with <number> depth'),
86 sub describe_cmdline_options {
89 my @cmdline = ( $self->SUPER::describe_cmdline_options(), @module_cmdline );
94 sub parse_cmdline_option {
95 my ($self, $opt) = @_;
96 return 1 if $self->SUPER::parse_cmdline_option($opt);
97 if ($opt =~ /^--git-ref=(.*)$/) {
98 push @{$self->{options}{git_ref}}, $1;
100 } elsif ($opt =~ /^--git-depth=(\d+)$/) {
101 $self->{options}{git_depth} = $1;
108 my ($self, $dir) = @_;
110 return (0, g_("doesn't contain a git repository")) unless -d "$dir/.git";
115 my ($self, $dir) = @_;
116 my $diff_ignore_regex = $self->{options}{diff_ignore_regex};
118 $dir =~ s{/+$}{}; # Strip trailing /
119 my ($dirname, $updir) = fileparse($dir);
120 my $basenamerev = $self->get_basename(1);
124 my $old_cwd = getcwd();
125 chdir $dir or syserr(g_("unable to chdir to '%s'"), $dir);
127 # Check for uncommitted files.
128 # To support dpkg-source -i, get a list of files
129 # equivalent to the ones git status finds, and remove any
130 # ignored files from it.
131 my @ignores = '--exclude-per-directory=.gitignore';
132 my $core_excludesfile = qx(git config --get core.excludesfile);
133 chomp $core_excludesfile;
134 if (length $core_excludesfile && -e $core_excludesfile) {
135 push @ignores, "--exclude-from=$core_excludesfile";
137 if (-e '.git/info/exclude') {
138 push @ignores, '--exclude-from=.git/info/exclude';
140 open(my $git_ls_files_fh, '-|', 'git', 'ls-files', '--modified', '--deleted',
141 '-z', '--others', @ignores) or subprocerr('git ls-files');
146 while (<$git_ls_files_fh>) {
148 if (! length $diff_ignore_regex ||
149 ! m/$diff_ignore_regex/o) {
154 close($git_ls_files_fh) or syserr(g_('git ls-files exited nonzero'));
156 error(g_('uncommitted, not-ignored changes in working directory: %s'),
160 # If a depth was specified, need to create a shallow clone and
164 if ($self->{options}{git_depth}) {
165 chdir $old_cwd or syserr(g_("unable to chdir to '%s'"), $old_cwd);
166 $tmp = tempdir("$dirname.git.XXXXXX", DIR => $updir);
167 push_exit_handler(sub { erasedir($tmp) });
168 my $clone_dir = "$tmp/repo.git";
169 # file:// is needed to avoid local cloning, which does not
170 # create a shallow clone.
171 info(g_('creating shallow clone with depth %s'),
172 $self->{options}{git_depth});
173 system('git', 'clone', '--depth=' . $self->{options}{git_depth},
174 '--quiet', '--bare', 'file://' . abs_path($dir), $clone_dir);
175 subprocerr('git clone') if $?;
177 or syserr(g_("unable to chdir to '%s'"), $clone_dir);
178 $shallowfile = "$basenamerev.gitshallow";
179 system('cp', '-f', 'shallow', "$old_cwd/$shallowfile");
180 subprocerr('cp shallow') if $?;
183 # Create the git bundle.
184 my $bundlefile = "$basenamerev.git";
185 my @bundle_arg=$self->{options}{git_ref} ?
186 (@{$self->{options}{git_ref}}) : '--all';
187 info(g_('bundling: %s'), join(' ', @bundle_arg));
188 system('git', 'bundle', 'create', "$old_cwd/$bundlefile",
190 'HEAD', # ensure HEAD is included no matter what
191 '--', # avoids ambiguity error when referring to eg, a debian branch
193 subprocerr('git bundle') if $?;
195 chdir $old_cwd or syserr(g_("unable to chdir to '%s'"), $old_cwd);
202 $self->add_file($bundlefile);
203 if (defined $shallowfile) {
204 $self->add_file($shallowfile);
209 my ($self, $newdirectory) = @_;
210 my $fields = $self->{fields};
212 my $dscdir = $self->{basedir};
213 my $basenamerev = $self->get_basename(1);
215 my @files = $self->get_files();
216 my ($bundle, $shallow);
217 foreach my $file (@files) {
218 if ($file =~ /^\Q$basenamerev\E\.git$/) {
219 if (! defined $bundle) {
222 error(g_('format v3.0 (git) uses only one .git file'));
224 } elsif ($file =~ /^\Q$basenamerev\E\.gitshallow$/) {
225 if (! defined $shallow) {
228 error(g_('format v3.0 (git) uses only one .gitshallow file'));
231 error(g_('format v3.0 (git) unknown file: %s', $file));
234 if (! defined $bundle) {
235 error(g_('format v3.0 (git) expected %s'), "$basenamerev.git");
238 if ($self->{options}{no_overwrite_dir} and -e $newdirectory) {
239 error(g_('unpack target exists: %s'), $newdirectory);
241 erasedir($newdirectory);
244 # Extract git bundle.
245 info(g_('cloning %s'), $bundle);
246 system('git', 'clone', '--quiet', $dscdir . $bundle, $newdirectory);
247 subprocerr('git bundle') if $?;
249 if (defined $shallow) {
250 # Move shallow info file into place, so git does not
251 # try to follow parents of shallow refs.
252 info(g_('setting up shallow clone'));
253 system('cp', '-f', $dscdir . $shallow, "$newdirectory/.git/shallow");
254 subprocerr('cp') if $?;
257 _sanity_check($newdirectory);