Commit | Line | Data |
---|---|---|
089f11fb | 1 | #!/usr/bin/perl |
bcd49ef3 LW |
2 | use strict; |
3 | use warnings; | |
089f11fb JD |
4 | # On Ubuntu, you'll want the following packages: |
5 | # - libconfig-simple-perl | |
6 | # - libgit-repository-perl | |
7 | # - libdate-manip-perl | |
8 | use Config::Simple; | |
9 | use File::Find; | |
10 | use File::stat; | |
11 | use Date::Manip qw/ParseDate UnixDate/; | |
12 | use Time::localtime; | |
13 | use Git::Repository; | |
14 | use Git::Repository::Command; | |
15 | use Getopt::Long; | |
16 | use Data::Dumper; | |
17 | ||
bcd49ef3 | 18 | my ($help, $config_file, $repo_path, $all, $check_files, $hash_file, $since, $git_output, $deployed_output); |
089f11fb JD |
19 | my $branch = 'HEAD'; |
20 | my $remote = 'origin'; | |
21 | my @components; | |
22 | ||
23 | GetOptions( | |
24 | 'help' => \$help, # show help message and exit | |
25 | 'config=s' => \$config_file, # INI file for path mappings | |
26 | 'repo=s' => \$repo_path, # location of git repo | |
27 | 'branch=s' => \$branch, # git branch/head to check against (defaults to 'HEAD') | |
28 | 'remote=s' => \$remote, # git remote to pull from (defaults to 'origin') | |
29 | 'component=s' => \@components, # parts of EG to be checked (each component is a block in the INI file) | |
30 | 'all' => \$all, # check all components specified in config (overrides --component) | |
31 | 'check-files' => \$check_files, # check deployed files | |
32 | 'hash-file=s' => \$hash_file, # file containing git hashes (overrides --repo) | |
33 | 'since=s' => \$since, # check only files modified since this time | |
0d95709d JD |
34 | 'print-git-hashes=s' => \$git_output, # output file for git hashes (optional) |
35 | 'print-deployed-hashes=s' => \$deployed_output # output file for hashes of deployed files (optional) | |
089f11fb JD |
36 | ); |
37 | ||
38 | if ($help) { | |
39 | print <<"HELP"; | |
40 | USAGE: | |
41 | $0 --config pathmap.ini --repo /path/to/evergreen.git [ --component perl [ --component tt2 ] | --all ] --check-files [ --since <date> ] | |
42 | $0 --config pathmap.ini --repo /path/to/evergreen.git [ --component perl [ --component tt2 ... ] | --all ] --print-git-hashes <git-hashes.txt> | |
43 | $0 --config pathmap.ini --repo /path/to/evergreen.git [ --component perl [ --component tt2 ... ] | --all ] --print-deployed-hashes <deployed-hashes.txt> | |
44 | ||
45 | OPTIONS: | |
46 | --help | |
47 | Show help message and exit. | |
48 | --config | |
49 | Location of INI file for path mappings. | |
50 | --repo | |
51 | Location of git repo (overridden by --hash-file). | |
52 | --branch | |
53 | Git branch (head) to check against (defaults to HEAD). | |
54 | --remote | |
55 | Git remote to pull from (defaults to origin). | |
56 | --component | |
57 | Parts of EG to be checked (each component is a block in the config file). | |
58 | You can use this option multiple times: --component perl --component web | |
59 | --all | |
60 | Check all components specified in config file (overrides --component). | |
61 | --check-files | |
62 | Get file hashes from git, then check deployed files to see if they match. | |
63 | --hash-file | |
64 | File containing file hashes from git. If you use this option, you don't | |
65 | need to specify a git repo using the --repo option. | |
66 | Use case: pull hashes from git once, copy the resulting file to multiple | |
67 | servers, then check the deployed code against the file instead of pulling | |
68 | hashes from git individually on each server. | |
69 | --since | |
70 | Only calculate hashes if file has been modified since the specified time. | |
71 | --print-git-hashes | |
72 | Get file hashes from git repo and print/append to the specified file. | |
73 | Can be used with --check-files and --print-deployed-hashes. | |
74 | --print-deployed-hashes | |
75 | Print git-like hashes for deployed files. | |
76 | Can be used with --check-files and --print-git-hashes. | |
77 | ||
78 | HELP | |
79 | exit; | |
80 | } | |
81 | ||
82 | # specify all possible components (--all option); | |
c6a6221d | 83 | @components = split('\n', `./access_pathmap.pl --config $config_file`) if ($all); |
089f11fb JD |
84 | |
85 | if ($git_output) { | |
86 | open (GITOUTPUT, '>>', $git_output) or die "Could not open $git_output: $!\n"; | |
87 | } | |
88 | if ($deployed_output) { | |
89 | open (DEPLOYEDOUTPUT, '>>', $deployed_output) or die "Could not open $deployed_output: $!\n"; | |
90 | } | |
91 | ||
92 | my %git_hashes; | |
93 | ||
94 | # optionally read in git hashes from file | |
95 | if ($hash_file) { | |
96 | open (HASHFILE, '<', $hash_file) or die "Could not open $hash_file: $!\n"; | |
97 | while (<HASHFILE>) { | |
88d13ce6 | 98 | chomp; |
089f11fb JD |
99 | my ($hash, $file) = split(/\s+/, $_, 2); |
100 | $git_hashes{$file} = $hash; | |
101 | } | |
102 | close HASHFILE; | |
103 | } | |
104 | ||
105 | foreach my $component (@components) { | |
bcd49ef3 | 106 | my @paths = split('\n', `./access_pathmap.pl --config $config_file --component $component`); |
089f11fb JD |
107 | |
108 | # if no hash file was supplied, grab git hashes from repo | |
109 | if (!$hash_file) { | |
110 | ||
111 | # load git repo | |
112 | die "No repo specified\n" unless ($repo_path); | |
113 | $repo_path =~ s|/$||; | |
114 | $repo_path = "$repo_path/.git" unless ($repo_path =~ /\.git$/); | |
115 | my $repo = Git::Repository->new( git_dir => $repo_path ) or die "Could not load git repo $repo_path: $!\n"; | |
116 | ||
117 | # ensure git repo is up-to-date | |
118 | if ($branch ne 'HEAD') { | |
119 | $repo->run( 'pull' => $remote ); | |
120 | $repo->run( 'checkout' => $branch ); # TODO: is this necessary? | |
121 | } | |
122 | ||
123 | # get hashes from git | |
bcd49ef3 | 124 | foreach my $srcpath (@paths) { |
089f11fb JD |
125 | # use git-ls-tree to traverse the file tree starting at $srcpath |
126 | # e.g. `git ls-tree -r HEAD Open-ILS/src/perlmods/lib` | |
127 | my @tree = $repo->run( 'ls-tree' => '-r', $branch, $srcpath ); | |
128 | foreach my $file (@tree) { | |
129 | my ($mode, $type, $hash, $filename) = split(/\s+/, $file, 4); | |
130 | $git_hashes{$filename} = $hash; | |
131 | print GITOUTPUT $hash, "\t", $filename, "\n" if ($git_output); | |
132 | } | |
133 | } | |
134 | } | |
135 | ||
136 | # check deployed files | |
137 | if ($check_files || $deployed_output) { | |
bcd49ef3 LW |
138 | foreach my $srcpath (@paths) { |
139 | my $destpath = `./access_pathmap.pl --config $config_file --component $component --srcpath $srcpath`; | |
089f11fb JD |
140 | |
141 | # for each file in the destination path, push the file's absolute path to @files; | |
142 | # output will include symlinked files, but will not include directories | |
bcd49ef3 LW |
143 | # clear @files for each time through loop |
144 | my @files = (); | |
089f11fb JD |
145 | find( { wanted => sub { push @files, $_ if -f }, follow => 1, no_chdir => 1 }, $destpath ); |
146 | ||
147 | foreach my $file (@files) { | |
089f11fb JD |
148 | if ($since) { |
149 | # convert $since to seconds since epoch | |
150 | my $since_ts = UnixDate($since, '%s'); | |
151 | # get $file modification time as seconds since epoch | |
152 | my $file_ts = ctime(stat($file)->mtime); | |
153 | ||
154 | next unless $file_ts > $since_ts; | |
155 | } | |
156 | ||
157 | # you can calculate what the git hash would be | |
158 | # for any file using `git hash-object <file>`; | |
159 | # you don't even need to be in a git repo to run it! | |
160 | my $hash = Git::Repository->run( 'hash-object', $file ); | |
161 | ||
162 | if ($check_files) { | |
163 | my $srcfile = $file; | |
164 | $srcfile =~ s|^$destpath|$srcpath|; | |
165 | ||
166 | if (!$git_hashes{$srcfile}) { | |
167 | print "untracked\t$file\n"; | |
168 | } elsif ($git_hashes{$srcfile} ne $hash) { | |
169 | print "modified\t$file\n"; | |
170 | } | |
171 | } | |
bcd49ef3 | 172 | print DEPLOYEDOUTPUT $hash, "\t", $file, "\n" if ($deployed_output); |
089f11fb JD |
173 | |
174 | } | |
175 | } | |
176 | } | |
177 | } | |
178 | ||
179 | close (GITOUTPUT) if ($git_output); | |
180 | close (DEPLOYEDOUTPUT) if ($deployed_output); | |
181 |