1 /*
  2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 package org.openjdk.skara.cli;
 24 
 25 import org.openjdk.skara.args.*;
 26 import org.openjdk.skara.vcs.*;
 27 
 28 import java.io.*;
 29 import java.net.URI;
 30 import java.nio.file.*;
 31 import java.util.*;
 32 import java.util.logging.*;
 33 
 34 public class GitSync {
 35     private static IOException die(String message) {
 36         System.err.println(message);
 37         System.exit(1);
 38         return new IOException("will never reach here");
 39     }
 40 
 41     private static int pull() throws IOException, InterruptedException {
 42         var pb = new ProcessBuilder("git", "pull");
 43         pb.inheritIO();
 44         return pb.start().waitFor();
 45     }
 46 
 47     public static void main(String[] args) throws IOException, InterruptedException {
 48         var flags = List.of(
 49             Option.shortcut("")
 50                   .fullname("from")
 51                   .describe("REMOTE")
 52                   .helptext("Fetch changes from this remote")
 53                   .optional(),
 54             Option.shortcut("")
 55                   .fullname("to")
 56                   .describe("REMOTE")
 57                   .helptext("Push changes to this remote")
 58                   .optional(),
 59             Option.shortcut("")
 60                   .fullname("branches")
 61                   .describe("BRANCHES")
 62                   .helptext("Comma separated list of branches to sync")
 63                   .optional(),
 64             Switch.shortcut("")
 65                   .fullname("pull")
 66                   .helptext("Pull current branch from origin after successful sync")
 67                   .optional(),
 68             Switch.shortcut("m")
 69                   .fullname("mercurial")
 70                   .helptext("Force use of mercurial")
 71                   .optional(),
 72             Switch.shortcut("")
 73                   .fullname("verbose")
 74                   .helptext("Turn on verbose output")
 75                   .optional(),
 76             Switch.shortcut("")
 77                   .fullname("debug")
 78                   .helptext("Turn on debugging output")
 79                   .optional(),
 80             Switch.shortcut("v")
 81                   .fullname("version")
 82                   .helptext("Print the version of this tool")
 83                   .optional()
 84         );
 85 
 86         var parser = new ArgumentParser("git sync", flags);
 87         var arguments = parser.parse(args);
 88 
 89         if (arguments.contains("version")) {
 90             System.out.println("git-sync version: " + Version.fromManifest().orElse("unknown"));
 91             System.exit(0);
 92         }
 93 
 94         if (arguments.contains("verbose") || arguments.contains("debug")) {
 95             var level = arguments.contains("debug") ? Level.FINER : Level.FINE;
 96             Logging.setup(level);
 97         }
 98 
 99         var cwd = Paths.get("").toAbsolutePath();
100         var repo = Repository.get(cwd).orElseThrow(() ->
101                 die("error: no repository found at " + cwd.toString())
102         );
103 
104         var remotes = repo.remotes();
105 
106         String upstream = null;
107         if (arguments.contains("from")) {
108             upstream = arguments.get("from").asString();
109         } else {
110             var lines = repo.config("sync.from");
111             if (lines.size() == 1 && remotes.contains(lines.get(0))) {
112                 upstream = lines.get(0);
113             } else {
114                 die("No remote provided to fetch from, please set the --from flag");
115             }
116         }
117         var upstreamPullPath = remotes.contains(upstream) ?
118             Remote.toURI(repo.pullPath(upstream)) : URI.create(upstream);
119 
120         String origin = null;
121         if (arguments.contains("to")) {
122             origin = arguments.get("to").asString();
123         } else {
124             var lines = repo.config("sync.to");
125             if (lines.size() == 1) {
126                 if (!remotes.contains(lines.get(0))) {
127                     die("The given remote to push to, " + lines.get(0) + ", does not exist");
128                 } else {
129                     origin = lines.get(0);
130                 }
131             } else {
132                 origin = "origin";
133             }
134         }
135         var originPushPath = Remote.toURI(repo.pushPath(origin));
136 
137         var branches = new HashSet<String>();
138         if (arguments.contains("branches")) {
139             var requested = arguments.get("branches").asString().split(",");
140             for (var branch : requested) {
141                 branches.add(branch.trim());
142             }
143         }
144 
145         for (var branch : repo.remoteBranches(upstream)) {
146             var name = branch.name();
147             if (!branches.isEmpty() && !branches.contains(name)) {
148                 System.out.println("Skipping branch " + name);
149                 continue;
150             }
151             System.out.print("Syncing " + upstream + "/" + name + " to " + origin + "/" + name + "... ");
152             System.out.flush();
153             var fetchHead = repo.fetch(upstreamPullPath, branch.hash().hex());
154             repo.push(fetchHead, originPushPath, name);
155             System.out.println("done");
156         }
157 
158         }
159 
160         if (arguments.contains("pull")) {
161             int err = pull();
162             if (err != 0) {
163                 System.exit(err);
164             }
165         }
166     }
167 }