8 Copyright (c) 2003-2010, Andrew Dunstan
10 See accompanying License file for license details
14 ##########################################################################
16 # SCM Class and subclasses for specific SCMs (currently CVS and git).
18 #########################################################################
20 package EximBuild::SCM;
22 use vars qw($VERSION); $VERSION = 'REL_0.1';
24 # factory function to return the right subclass
29 my $target = shift || 'exim';
30 if (defined($conf->{scm}) && $conf->{scm} =~ /^git$/i)
33 return new EximBuild::SCM::Git $conf, $target;
35 #elsif ((defined($conf->{scm}) && $conf->{scm} =~ /^cvs$/i )
37 # ||$conf->{cvsmethod})
39 # $conf->{scm} = 'cvs';
40 # return new EximBuild::SCM::CVS $conf, $target;
42 die "only Git currently supported";
45 # common routine use for copying the source, called by the
46 # SCM objects (directly, not as class methods)
49 my $using_msvc = shift;
51 my $build_path = shift;
53 # annoyingly, there isn't a standard perl module to do a recursive copy
54 # and I don't want to require use of the non-standard File::Copy::Recursive
55 system("cp -r $target $build_path 2>&1");
57 die "copying directories: $status" if $status;
61 # required operations in each subclass:
63 # copy_source_required()
73 ##################################
77 ##################################
79 package EximBuild::SCM::Git;
90 $self->{gitrepo} = $conf->{scmrepo} ||
91 "git://git.exim.org/exim.git";
92 $self->{reference} = $conf->{git_reference}
93 if defined($conf->{git_reference});
96 ? "$conf->{build_root}/exim.git"
97 :"$conf->{build_root}/$target-exim.git"
98 )if $conf->{git_keep_mirror};
99 $self->{ignore_mirror_failure} = $conf->{git_ignore_mirror_failure};
100 $self->{target} = $target;
101 return bless $self, $class;
104 sub copy_source_required
115 my $using_msvc = shift;
116 my $target = $self->{target};
117 my $build_path = $self->{build_path};
118 die "no build path" unless $build_path;
120 # we don't want to copy the (very large) .git directory
121 # so we just move it out of the way during the copy
122 # there might be better ways of doing this, but this should do for now
124 move "$target/.git", "./git-save";
125 EximBuild::SCM::copy_source($using_msvc,$target,$build_path);
126 move "./git-save","$target/.git";
132 my $use_vpath = shift; # irrelevant for git
133 my $target = $self->{target};
134 $self->{build_path} = "$target.$$";
135 return $self->{build_path};
148 main::writelog('githead',[$self->{headref}])
157 my $gitserver = $self->{gitrepo};
158 my $target = $self->{target};
161 # Msysgit does some horrible things, especially when it expects a drive
162 # spec and doesn't get one. So we extract it if it exists and use it
166 $drive = substr($cwd,0,2) if $cwd =~ /^[A-Z]:/;
172 my $mirror = $target eq 'exim' ? 'exim.git' : "$target-exim.git";
174 if (-d $self->{mirror})
176 @gitlog = `git --git-dir="$self->{mirror}" fetch 2>&1`;
177 $status = $self->{ignore_mirror_failure} ? 0 : $? >> 8;
181 my $char1 = substr($gitserver,0,1);
182 $gitserver = "$drive$gitserver"
183 if ( $char1 eq '/' or $char1 eq '\\');
185 # this will fail on older git versions
186 # workaround is to do this manually in the buildroot:
187 # git clone --bare $gitserver exim.git
188 # (cd exim.git && git remote add --mirror origin $gitserver)
189 # or equivalent for other targets
190 @gitlog = `git clone --mirror $gitserver $self->{mirror} 2>&1`;
195 unshift(@gitlog,"Git mirror failure:\n");
196 print @gitlog if ($main::verbose);
197 main::send_result('Git-mirror',$status,\@gitlog);
201 push @gitlog, "Git arguments:\n".
202 " branch=$branch gitserver=$gitserver target=$target\n\n";
206 # First check and see if git is in path
211 unshift @ret, "git not found in path\n";
212 main::send_result("$target-Git",$status,\@ret);
216 my @branches = `git branch 2>&1`;
217 unless (grep {/^\* bf_\Q$branch\E$/} @branches)
220 print "Missing checked out branch bf_$branch:\n",@branches
222 unshift @branches,"Missing checked out branch bf_$branch in " . cwd() . ":\n";
223 main::send_result("$target-Git",$status,\@branches);
225 my @pulllog = `git pull --rebase 2>&1`;
226 push(@gitlog,@pulllog);
232 defined($self->{reference}) ?"--reference $self->{reference}" : "";
234 my $base = $self->{mirror} || $gitserver;
236 my $char1 = substr($base,0,1);
237 $base = "$drive$base"
238 if ( $char1 eq '/' or $char1 eq '\\');
240 my @clonelog = `git clone -q $reference $base $target 2>&1`;
241 push(@gitlog,@clonelog);
247 # make sure we don't name the new branch HEAD
248 # also, safer to checkout origin/master than origin/HEAD, I think
249 my $rbranch = $branch eq 'HEAD' ? 'master' : $branch;
251 `git checkout -b bf_$branch --track origin/$rbranch 2>&1`;
252 push(@gitlog,@colog);
257 print "================== git log =====================\n",@gitlog
258 if ($main::verbose > 1);
260 # can't call writelog here because we call cleanlogs after the
261 # checkout stage, since we only clear out the logs if we find we need to
263 # consequence - we don't save the git log if we don't do a run
264 # doesn't matter too much because if git fails we exit anyway.
266 # Don't call git clean here. If the user has left stuff lying around it
267 # might be important to them, so instead of blowing it away just bitch
271 my @gitstat = `git status --porcelain 2>&1`;
274 my ($headref,$refhandle);
275 if (open($refhandle,"$target/.git/refs/heads/bf_$branch"))
277 $headref = <$refhandle>;
280 $self->{headref} = $headref;
283 main::send_result("$target-Git",$status,\@gitlog) if ($status);
284 unless ($main::nosend && $main::nostatus)
286 push(@gitlog,"===========",@gitstat);
287 main::send_result("$target-Git-Dirty",99,\@gitlog)
291 # if we were successful, however, we return the info so that
292 # we can put it in the newly cleaned logdir later on.
299 my $target = $self->{target};
301 system("git clean -dfxq");
305 # private Class level routine for getting changed file data
313 foreach my $line (@lines)
315 next if $line =~ /^(Author:|Date:|\s)/;
317 if ($line =~ /^commit ([0-9a-zA-Z]+)/)
324 # anything else should be a file name
325 $line =~ s/\s+$//; # make sure all trailing space is trimmed
326 $list->{$line} ||= $commit; # keep most recent commit
335 my $target = $self->{target};
336 my $current_snap = shift;
337 my $last_run_snap = shift;
338 my $last_success_snap = shift || 0;
339 my $changed_files = shift;
340 my $changed_since_success = shift;
342 my $cmd = qq{git --git-dir=$target/.git log -n 1 "--pretty=format:%ct"};
343 $$current_snap = `$cmd` +0;
345 # get the list of changed files and stash the commit data
349 if ($last_success_snap > 0 && $last_success_snap < $last_run_snap)
351 $last_success_snap++;
352 my $lrsscmd ="git --git-dir=$target/.git log --name-only "
353 ."--since=$last_success_snap --until=$last_run_snap";
354 $self->{changed_since_success} = parse_log($lrsscmd);
358 $self->{changed_since_success} = {};
361 my $lrscmd ="git --git-dir=$target/.git log --name-only "
362 ."--since=$last_run_snap";
363 $self->{changed_since_last_run} = parse_log($lrscmd);
364 foreach my $file (keys %{$self->{changed_since_last_run}})
366 delete $self->{changed_since_success}->{$file};
371 $self->{changed_since_last_run} = {};
374 @$changed_files = sort keys %{$self->{changed_since_last_run}};
375 @$changed_since_success = sort keys %{$self->{changed_since_success}};
382 return unless @$flist;
385 # for git we have already collected and stashed the info, so we just
386 # extract it from the stash.
388 foreach my $file (@$flist)
390 if (exists $self->{changed_since_last_run}->{$file})
392 my $commit = $self->{changed_since_last_run}->{$file};
393 push(@repoversions,"$file $commit");
395 elsif (exists $self->{changed_since_success}->{$file})
397 my $commit = $self->{changed_since_success}->{$file};
398 push(@repoversions,"$file $commit");
401 @$flist = @repoversions;