summaryrefslogtreecommitdiffstats
path: root/lib/Devscripts/Salsa/merge_request.pm
blob: 9044f00402dba58da54dfb626605f3aa90b642f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# Creates a merge request from current directory (or using parameters)
package Devscripts::Salsa::merge_request;

use strict;
use Devscripts::Output;
use Dpkg::IPC;
use Moo::Role;

with 'Devscripts::Salsa::search_project';

sub merge_request {
    my ($self, $dst_project, $dst_branch) = @_;
    my $src_branch  = $self->config->mr_src_branch;
    my $src_project = $self->config->mr_src_project;
    $dst_project ||= $self->config->mr_dst_project;
    $dst_branch  ||= $self->config->mr_dst_branch;
    my $title = $self->config->mr_title;
    my $desc  = $self->config->mr_desc;

    if ($src_branch) {
        unless ($src_project and $dst_project) {
            ds_warn "--mr-src-project and --mr-src-project "
              . "are required when --mr-src-branch is set";
            return 1;
        }
        unless ($src_project =~ m#/#) {
            $src_project = $self->project2path($src_project);
        }
    } else {    # Use current repository to find elements
        ds_verbose "using current branch as source";
        my $out;
        unless ($src_project) {
            # 1. Verify that repo is ready
            spawn(
                exec       => [qw(git status -s -b -uno)],
                wait_child => 1,
                to_string  => \$out
            );
            chomp $out;
            # Case "rebased"
            if ($out =~ /\[/) {
                ds_warn "Current branch isn't pushed, aborting:\n";
                return 1;
            }
            # Case else: nothing after src...dst
            unless ($out =~ /\s(\S+)\.\.\.(\S+)/s) {
                ds_warn
                  "Current branch as no origin or isn't pushed, aborting";
                return 1;
            }
            # 2. Set source branch to current branch
            $src_branch ||= $1;
            ds_verbose "Found current branch: $src_branch";
        }
        unless ($src_project and $dst_project) {
            # Check remote links
            spawn(
                exec       => [qw(git remote --verbose show)],
                wait_child => 1,
                to_string  => \$out,
            );
            my $origin = $self->config->api_url;
            $origin =~ s#api/v4$##;
            # 3. Set source project using "origin" target
            unless ($src_project) {
                if ($out
                    =~ /origin\s+(?:\Q$self->{config}->{git_server_url}\E|\Q$origin\E)(\S*)/m
                ) {
                    $src_project = $1;
                    $src_project =~ s/\.git$//;
                } else {
                    ds_warn
"Unable to find project origin, set it using --mr-src-project";
                    return 1;
                }
            }
            # 4. Steps to find destination project:
            #    - command-line
            #    - GitLab API (search for "forked_from_project"
            #    - "upstream" in git remote
            #    - use source project as destination project

            # 4.1. Stop if dest project has been given in command line
            unless ($dst_project) {
                my $project = $self->api->project($src_project);

                # 4.2. Search original project from GitLab API
                if ($project->{forked_from_project}) {
                    $dst_project
                      = $project->{forked_from_project}->{path_with_namespace};
                }
                if ($dst_project) {
                    ds_verbose "Project was forked from $dst_project";

                    # 4.3. Search for an "upstream" target in `git remote`
                } elsif ($out
                    =~ /upstream\s+(?:\Q$self->{config}->{git_server_url}\E|\Q$origin\E)(\S*)/m
                ) {
                    $dst_project = $1;
                    $dst_project =~ s/\.git$//;
                    ds_verbose 'Use "upstream" target as dst project';
                    # 4.4. Use source project as destination
                } else {
                    ds_warn
"No upstream target found, using current project as target";
                    $dst_project = $src_project;
                }
                ds_verbose "Use $dst_project as dest project";
            }
        }
        # 5. Search for MR title and desc
        unless ($title) {
            ds_warn "Title not set, using last commit";
            spawn(
                exec       => ['git', 'show', '--format=format:%s###%b'],
                wait_child => 1,
                to_string  => \$out,
            );
            $out =~ s/\ndiff.*$//s;
            my ($t, $d) = split /###/, $out;
            chomp $d;
            $title = $t;
            ds_verbose "Title set to $title";
            $desc ||= $d;
            # Replace all bug links by markdown links
            if ($desc) {
                $desc =~ s@#(\d{6,})\b@[#$1](https://bugs.debian.org/$1)@mg;
                ds_verbose "Desc set to $desc";
            }
        }
    }
    if ($dst_project eq 'same') {
        $dst_project = $src_project;
    }
    my $src = $self->api->project($src_project);
    unless ($title) {
        ds_warn "Title is required";
        return 1;
    }
    unless ($src and $src->{id}) {
        ds_warn "Target project not found $src_project";
        return 1;
    }
    my $dst;
    if ($dst_project) {
        $dst = $self->api->project($dst_project);
        unless ($dst and $dst->{id}) {
            ds_warn "Target project not found";
            return 1;
        }
    }
    return 1
      if (
        ds_prompt(
"You're going to push an MR to $dst_project:$dst_branch. Continue (Y/n)"
        ));
    my $res = $self->api->create_merge_request(
        $src->{id},
        {
            source_branch        => $src_branch,
            target_branch        => $dst_branch,
            title                => $title,
            remove_source_branch => $self->config->mr_remove_source_branch,
            squash               => $self->config->mr_allow_squash,
            ($dst  ? (target_project_id => $dst->{id}) : ()),
            ($desc ? (description       => $desc)      : ()),
        });
    ds_warn "MR '$title' posted:";
    ds_warn $res->{web_url};
    return 0;
}

1;