Skip to content

Commit d4c2eba

Browse files
committed
Inherit submodules from template repository content
Signed-off-by: Steffen Schröter <[email protected]>
1 parent 31f6b95 commit d4c2eba

24 files changed

+901
-0
lines changed

modules/git/submodule.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ type SubModule struct {
2222
URL string
2323
}
2424

25+
type SubModuleCommit struct {
26+
Name string
27+
Commit string
28+
}
29+
2530
// SubModuleFile represents a file with submodule type.
2631
type SubModuleFile struct {
2732
*Commit
@@ -122,3 +127,63 @@ func (sf *SubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) strin
122127
func (sf *SubModuleFile) RefID() string {
123128
return sf.refID
124129
}
130+
131+
func GetSubmoduleCommits(repoPath string) []SubModuleCommit {
132+
var submodules []SubModuleCommit
133+
134+
submoduleOut, err := NewCommand("config", "-f", ".gitmodules", "--list", "--name-only").
135+
RunInDir(repoPath)
136+
137+
if err != nil {
138+
// Command fails if there are no or invalid submodules, just return an empty list
139+
return submodules
140+
}
141+
142+
for _, line := range strings.Split(strings.TrimSuffix(submoduleOut, "\n"), "\n") {
143+
if len(line) < len("submodule.x.url") ||
144+
!strings.HasPrefix(line, "submodule.") ||
145+
!strings.HasSuffix(line, ".url") {
146+
147+
continue
148+
}
149+
150+
name := line[len("submodule.") : len(line)-len(".url")]
151+
name = strings.TrimSpace(name)
152+
153+
if len(name) == 0 {
154+
log("Submodule skipped because it has no name")
155+
continue
156+
}
157+
158+
commit, err := NewCommand("submodule", "status", name).
159+
RunInDir(repoPath)
160+
161+
// If no commit was found for the module skip it
162+
if err != nil {
163+
log("Submodule %s skipped because it has no commit", name)
164+
continue
165+
}
166+
167+
if len(commit) > 0 {
168+
commit = commit[1:]
169+
}
170+
171+
fields := strings.Fields(commit)
172+
173+
if len(fields) == 0 {
174+
log("Submodule %s skipped because it has no valid commit", name)
175+
continue
176+
}
177+
178+
commit = fields[0]
179+
180+
if len(commit) != 40 {
181+
log("Submodule %s skipped due to malformed commit hash", name)
182+
continue
183+
}
184+
185+
submodules = append(submodules, SubModuleCommit{name, commit})
186+
}
187+
188+
return submodules
189+
}

modules/git/submodule_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
package git
66

77
import (
8+
"path/filepath"
89
"testing"
910

11+
"code.gitea.io/gitea/modules/util"
1012
"github.com/stretchr/testify/assert"
1113
)
1214

@@ -41,3 +43,20 @@ func TestGetRefURL(t *testing.T) {
4143
assert.EqualValues(t, kase.expect, getRefURL(kase.refURL, kase.prefixURL, kase.parentPath, kase.SSHDomain))
4244
}
4345
}
46+
47+
func TestRepository_GetSubmoduleCommits(t *testing.T) {
48+
bareRepo1Path := filepath.Join(testReposDir, "repo4_submodules")
49+
clonedPath, err := cloneRepo(bareRepo1Path, testReposDir, "repo4_TestRepository_GetSubmoduleCommits")
50+
assert.NoError(t, err)
51+
defer util.RemoveAll(clonedPath)
52+
53+
submodules := GetSubmoduleCommits(clonedPath)
54+
55+
assert.EqualValues(t, len(submodules), 2)
56+
57+
assert.EqualValues(t, submodules[0].Name, "libtest")
58+
assert.EqualValues(t, submodules[0].Commit, "1234567890123456789012345678901234567890")
59+
60+
assert.EqualValues(t, submodules[1].Name, "<°)))><")
61+
assert.EqualValues(t, submodules[1].Commit, "d2932de67963f23d43e1c7ecf20173e92ee6c43c")
62+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ref: refs/heads/master
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[core]
2+
repositoryformatversion = 0
3+
filemode = true
4+
bare = true
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Unnamed repository; edit this file 'description' to name the repository.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/sh
2+
#
3+
# An example hook script to check the commit log message taken by
4+
# applypatch from an e-mail message.
5+
#
6+
# The hook should exit with non-zero status after issuing an
7+
# appropriate message if it wants to stop the commit. The hook is
8+
# allowed to edit the commit message file.
9+
#
10+
# To enable this hook, rename this file to "applypatch-msg".
11+
12+
. git-sh-setup
13+
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
14+
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
15+
:
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/sh
2+
#
3+
# An example hook script to check the commit log message.
4+
# Called by "git commit" with one argument, the name of the file
5+
# that has the commit message. The hook should exit with non-zero
6+
# status after issuing an appropriate message if it wants to stop the
7+
# commit. The hook is allowed to edit the commit message file.
8+
#
9+
# To enable this hook, rename this file to "commit-msg".
10+
11+
# Uncomment the below to add a Signed-off-by line to the message.
12+
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
13+
# hook is more suited to it.
14+
#
15+
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
16+
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
17+
18+
# This example catches duplicate Signed-off-by lines.
19+
20+
test "" = "$(grep '^Signed-off-by: ' "$1" |
21+
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
22+
echo >&2 Duplicate Signed-off-by lines.
23+
exit 1
24+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#!/usr/bin/perl
2+
3+
use strict;
4+
use warnings;
5+
use IPC::Open2;
6+
7+
# An example hook script to integrate Watchman
8+
# (https://facebook.github.io/watchman/) with git to speed up detecting
9+
# new and modified files.
10+
#
11+
# The hook is passed a version (currently 2) and last update token
12+
# formatted as a string and outputs to stdout a new update token and
13+
# all files that have been modified since the update token. Paths must
14+
# be relative to the root of the working tree and separated by a single NUL.
15+
#
16+
# To enable this hook, rename this file to "query-watchman" and set
17+
# 'git config core.fsmonitor .git/hooks/query-watchman'
18+
#
19+
my ($version, $last_update_token) = @ARGV;
20+
21+
# Uncomment for debugging
22+
# print STDERR "$0 $version $last_update_token\n";
23+
24+
# Check the hook interface version
25+
if ($version ne 2) {
26+
die "Unsupported query-fsmonitor hook version '$version'.\n" .
27+
"Falling back to scanning...\n";
28+
}
29+
30+
my $git_work_tree = get_working_dir();
31+
32+
my $retry = 1;
33+
34+
my $json_pkg;
35+
eval {
36+
require JSON::XS;
37+
$json_pkg = "JSON::XS";
38+
1;
39+
} or do {
40+
require JSON::PP;
41+
$json_pkg = "JSON::PP";
42+
};
43+
44+
launch_watchman();
45+
46+
sub launch_watchman {
47+
my $o = watchman_query();
48+
if (is_work_tree_watched($o)) {
49+
output_result($o->{clock}, @{$o->{files}});
50+
}
51+
}
52+
53+
sub output_result {
54+
my ($clockid, @files) = @_;
55+
56+
# Uncomment for debugging watchman output
57+
# open (my $fh, ">", ".git/watchman-output.out");
58+
# binmode $fh, ":utf8";
59+
# print $fh "$clockid\n@files\n";
60+
# close $fh;
61+
62+
binmode STDOUT, ":utf8";
63+
print $clockid;
64+
print "\0";
65+
local $, = "\0";
66+
print @files;
67+
}
68+
69+
sub watchman_clock {
70+
my $response = qx/watchman clock "$git_work_tree"/;
71+
die "Failed to get clock id on '$git_work_tree'.\n" .
72+
"Falling back to scanning...\n" if $? != 0;
73+
74+
return $json_pkg->new->utf8->decode($response);
75+
}
76+
77+
sub watchman_query {
78+
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
79+
or die "open2() failed: $!\n" .
80+
"Falling back to scanning...\n";
81+
82+
# In the query expression below we're asking for names of files that
83+
# changed since $last_update_token but not from the .git folder.
84+
#
85+
# To accomplish this, we're using the "since" generator to use the
86+
# recency index to select candidate nodes and "fields" to limit the
87+
# output to file names only. Then we're using the "expression" term to
88+
# further constrain the results.
89+
if (substr($last_update_token, 0, 1) eq "c") {
90+
$last_update_token = "\"$last_update_token\"";
91+
}
92+
my $query = <<" END";
93+
["query", "$git_work_tree", {
94+
"since": $last_update_token,
95+
"fields": ["name"],
96+
"expression": ["not", ["dirname", ".git"]]
97+
}]
98+
END
99+
100+
# Uncomment for debugging the watchman query
101+
# open (my $fh, ">", ".git/watchman-query.json");
102+
# print $fh $query;
103+
# close $fh;
104+
105+
print CHLD_IN $query;
106+
close CHLD_IN;
107+
my $response = do {local $/; <CHLD_OUT>};
108+
109+
# Uncomment for debugging the watch response
110+
# open ($fh, ">", ".git/watchman-response.json");
111+
# print $fh $response;
112+
# close $fh;
113+
114+
die "Watchman: command returned no output.\n" .
115+
"Falling back to scanning...\n" if $response eq "";
116+
die "Watchman: command returned invalid output: $response\n" .
117+
"Falling back to scanning...\n" unless $response =~ /^\{/;
118+
119+
return $json_pkg->new->utf8->decode($response);
120+
}
121+
122+
sub is_work_tree_watched {
123+
my ($output) = @_;
124+
my $error = $output->{error};
125+
if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
126+
$retry--;
127+
my $response = qx/watchman watch "$git_work_tree"/;
128+
die "Failed to make watchman watch '$git_work_tree'.\n" .
129+
"Falling back to scanning...\n" if $? != 0;
130+
$output = $json_pkg->new->utf8->decode($response);
131+
$error = $output->{error};
132+
die "Watchman: $error.\n" .
133+
"Falling back to scanning...\n" if $error;
134+
135+
# Uncomment for debugging watchman output
136+
# open (my $fh, ">", ".git/watchman-output.out");
137+
# close $fh;
138+
139+
# Watchman will always return all files on the first query so
140+
# return the fast "everything is dirty" flag to git and do the
141+
# Watchman query just to get it over with now so we won't pay
142+
# the cost in git to look up each individual file.
143+
my $o = watchman_clock();
144+
$error = $output->{error};
145+
146+
die "Watchman: $error.\n" .
147+
"Falling back to scanning...\n" if $error;
148+
149+
output_result($o->{clock}, ("/"));
150+
$last_update_token = $o->{clock};
151+
152+
eval { launch_watchman() };
153+
return 0;
154+
}
155+
156+
die "Watchman: $error.\n" .
157+
"Falling back to scanning...\n" if $error;
158+
159+
return 1;
160+
}
161+
162+
sub get_working_dir {
163+
my $working_dir;
164+
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
165+
$working_dir = Win32::GetCwd();
166+
$working_dir =~ tr/\\/\//;
167+
} else {
168+
require Cwd;
169+
$working_dir = Cwd::cwd();
170+
}
171+
172+
return $working_dir;
173+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/sh
2+
#
3+
# An example hook script to prepare a packed repository for use over
4+
# dumb transports.
5+
#
6+
# To enable this hook, rename this file to "post-update".
7+
8+
exec git update-server-info
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/sh
2+
#
3+
# An example hook script to verify what is about to be committed
4+
# by applypatch from an e-mail message.
5+
#
6+
# The hook should exit with non-zero status after issuing an
7+
# appropriate message if it wants to stop the commit.
8+
#
9+
# To enable this hook, rename this file to "pre-applypatch".
10+
11+
. git-sh-setup
12+
precommit="$(git rev-parse --git-path hooks/pre-commit)"
13+
test -x "$precommit" && exec "$precommit" ${1+"$@"}
14+
:

0 commit comments

Comments
 (0)