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 fetch() throws IOException, InterruptedException {
 42         var pb = new ProcessBuilder("git", "fetch");
 43         pb.inheritIO();
 44         return pb.start().waitFor();
 45     }
 46 
 47     private static int pull() throws IOException, InterruptedException {
 48         var pb = new ProcessBuilder("git", "pull");
 49         pb.inheritIO();
 50         return pb.start().waitFor();
 51     }
 52 
 53     public static void main(String[] args) throws IOException, InterruptedException {
 54         var flags = List.of(
 55             Option.shortcut("")
 56                   .fullname("from")
 57                   .describe("REMOTE")
 58                   .helptext("Fetch changes from this remote")
 59                   .optional(),
 60             Option.shortcut("")
 61                   .fullname("to")
 62                   .describe("REMOTE")
 63                   .helptext("Push changes to this remote")
 64                   .optional(),
 65             Option.shortcut("")
 66                   .fullname("branches")
 67                   .describe("BRANCHES")
 68                   .helptext("Comma separated list of branches to sync")
 69                   .optional(),
 70             Switch.shortcut("")
 71                   .fullname("pull")
 72                   .helptext("Pull current branch from origin after successful sync")
 73                   .optional(),
 74             Switch.shortcut("")
 75                   .fullname("fetch")
 76                   .helptext("Fetch current branch from origin after successful sync")
 77                   .optional(),
 78             Switch.shortcut("m")
 79                   .fullname("mercurial")
 80                   .helptext("Force use of mercurial")
 81                   .optional(),
 82             Switch.shortcut("")
 83                   .fullname("verbose")
 84                   .helptext("Turn on verbose output")
 85                   .optional(),
 86             Switch.shortcut("")
 87                   .fullname("debug")
 88                   .helptext("Turn on debugging output")
 89                   .optional(),
 90             Switch.shortcut("v")
 91                   .fullname("version")
 92                   .helptext("Print the version of this tool")
 93                   .optional()
 94         );
 95 
 96         var parser = new ArgumentParser("git sync", flags);
 97         var arguments = parser.parse(args);
 98 
 99         if (arguments.contains("version")) {
100             System.out.println("git-sync version: " + Version.fromManifest().orElse("unknown"));
101             System.exit(0);
102         }
103 
104         if (arguments.contains("verbose") || arguments.contains("debug")) {
105             var level = arguments.contains("debug") ? Level.FINER : Level.FINE;
106             Logging.setup(level);
107         }
108 
109         var cwd = Paths.get("").toAbsolutePath();
110         var repo = Repository.get(cwd).orElseThrow(() ->
111                 die("error: no repository found at " + cwd.toString())
112         );
113 
114         var remotes = repo.remotes();
115 
116         String upstream = null;
117         if (arguments.contains("from")) {
118             upstream = arguments.get("from").asString();
119         } else {
120             var lines = repo.config("sync.from");
121             if (lines.size() == 1 && remotes.contains(lines.get(0))) {
122                 upstream = lines.get(0);
123             } else {
124                 die("No remote provided to fetch from, please set the --from flag");
125             }
126         }
127         var upstreamPullPath = remotes.contains(upstream) ?
128             Remote.toURI(repo.pullPath(upstream)) : URI.create(upstream);
129 
130         String origin = null;
131         if (arguments.contains("to")) {
132             origin = arguments.get("to").asString();
133         } else {
134             var lines = repo.config("sync.to");
135             if (lines.size() == 1) {
136                 if (!remotes.contains(lines.get(0))) {
137                     die("The given remote to push to, " + lines.get(0) + ", does not exist");
138                 } else {
139                     origin = lines.get(0);
140                 }
141             } else {
142                 origin = "origin";
143             }
144         }
145         var originPushPath = Remote.toURI(repo.pushPath(origin));
146 
147         var branches = new HashSet<String>();
148         if (arguments.contains("branches")) {
149             var requested = arguments.get("branches").asString().split(",");
150             for (var branch : requested) {
151                 branches.add(branch.trim());
152             }
153         }
154 
155         for (var branch : repo.remoteBranches(upstream)) {
156             var name = branch.name();
157             if (!branches.isEmpty() && !branches.contains(name)) {
158                 System.out.println("Skipping branch " + name);
159                 continue;
160             }
161             System.out.print("Syncing " + upstream + "/" + name + " to " + origin + "/" + name + "... ");
162             System.out.flush();
163             var fetchHead = repo.fetch(upstreamPullPath, branch.hash().hex());
164             repo.push(fetchHead, originPushPath, name);
165             System.out.println("done");
166         }
167 
168         if (arguments.contains("fetch")) {
169             int err = fetch();
170             if (err != 0) {
171                 System.exit(err);
172             }
173         }
174 
175         if (arguments.contains("pull")) {
176             int err = pull();
177             if (err != 0) {
178                 System.exit(err);
179             }
180         }
181     }
182 }