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.host.*; 27 import org.openjdk.skara.vcs.Repository; 28 import org.openjdk.skara.proxy.HttpProxy; 29 30 import java.io.IOException; 31 import java.net.URI; 32 import java.nio.file.*; 33 import java.util.List; 34 import java.util.function.Supplier; 35 import java.util.logging.Level; 36 37 public class GitFork { 38 private static void exit(String fmt, Object...args) { 39 System.err.println(String.format(fmt, args)); 40 System.exit(1); 41 } 42 43 private static <T> Supplier<T> die(String fmt, Object... args) { 44 return () -> { 45 exit(fmt, args); 46 return null; 47 }; 48 } 49 50 private static void sleep(int ms) { 51 try { 52 Thread.sleep(ms); 53 } catch (InterruptedException e) { 54 // do nothing 55 } 56 } 57 58 private static Repository clone(URI from, Path dest, boolean isMercurial) throws IOException { 59 try { 60 var to = dest == null ? Path.of(from.getPath()).getFileName() : dest; 61 if (to.toString().endsWith(".git")) { 62 to = Path.of(to.toString().replace(".git", "")); 63 } 64 65 var vcs = isMercurial ? "hg" : "git"; 66 var pb = new ProcessBuilder(vcs, "clone", from.toString(), to.toString()); 67 pb.inheritIO(); 68 var p = pb.start(); 69 var res = p.waitFor(); 70 if (res != 0) { 71 exit("'" + vcs + " clone " + from.toString() + " " + to.toString() + "' failed with exit code: " + res); 72 } 73 return Repository.get(to).orElseThrow(() -> new IOException("Could not find repository")); 74 } catch (InterruptedException e) { 75 throw new IOException(e); 76 } 77 } 78 79 public static void main(String[] args) throws IOException { 80 var flags = List.of( 81 Option.shortcut("u") 82 .fullname("username") 83 .describe("NAME") 84 .helptext("Username on host") 85 .optional(), 86 Switch.shortcut("") 87 .fullname("verbose") 88 .helptext("Turn on verbose output") 89 .optional(), 90 Switch.shortcut("") 91 .fullname("debug") 92 .helptext("Turn on debugging output") 93 .optional(), 94 Switch.shortcut("") 95 .fullname("version") 96 .helptext("Print the version of this tool") 97 .optional(), 98 Switch.shortcut("") 99 .fullname("mercurial") 100 .helptext("Force use of mercurial") 101 .optional()); 102 103 var inputs = List.of( 104 Input.position(0) 105 .describe("URI") 106 .singular() 107 .required(), 108 Input.position(1) 109 .describe("NAME") 110 .singular() 111 .optional()); 112 113 var parser = new ArgumentParser("git-fork", flags, inputs); 114 var arguments = parser.parse(args); 115 var isMercurial = arguments.contains("mercurial"); 116 117 if (arguments.contains("version")) { 118 System.out.println("git-fork version: " + Version.fromManifest().orElse("unknown")); 119 System.exit(0); 120 } 121 122 if (arguments.contains("verbose") || arguments.contains("debug")) { 123 var level = arguments.contains("debug") ? Level.FINER : Level.FINE; 124 Logging.setup(level); 125 } 126 127 HttpProxy.setup(); 128 129 final var uri = URI.create(arguments.at(0).or(die("No URI for upstream repository provided")).asString()); 130 if (uri == null) { 131 exit("Not a valid URI: " + uri); 132 } 133 final var hostName = uri.getHost(); 134 var path = uri.getPath(); 135 final var protocol = uri.getScheme(); 136 final var token = isMercurial ? System.getenv("HG_TOKEN") : System.getenv("GIT_TOKEN"); 137 final var username = arguments.contains("username") ? arguments.get("username").asString() : null; 138 final var credentials = GitCredentials.fill(hostName, path, username, token, protocol); 139 140 if (credentials.password() == null) { 141 exit("No token for host " + hostName + " found, use git-credentials or the environment variable GIT_TOKEN"); 142 } 143 144 if (credentials.username() == null) { 145 exit("No username for host " + hostName + " found, use git-credentials or the flag --username"); 146 } 147 148 var host = Host.from(uri, new PersonalAccessToken(credentials.username(), credentials.password())); 149 if (path.endsWith(".git")) { 150 path = path.substring(0, path.length() - 4); 151 } 152 if (path.startsWith("/")) { 153 path = path.substring(1); 154 } 155 156 var fork = host.getRepository(path).fork(); 157 158 if (token == null) { 159 GitCredentials.approve(credentials); 160 } 161 162 var webUrl = fork.getWebUrl(); 163 if (isMercurial) { 164 webUrl = URI.create("git+" + webUrl.toString()); 165 } 166 if (arguments.at(1).isPresent()) { 167 System.out.println("Fork available at: " + fork.getWebUrl()); 168 var dest = arguments.at(1).asString(); 169 System.out.println("Cloning " + webUrl + "..."); 170 var repo = clone(webUrl, Path.of(dest), isMercurial); 171 var remoteWord = isMercurial ? "path" : "remote"; 172 System.out.print("Adding " + remoteWord + " 'upstream' for " + uri.toString() + "..."); 173 var upstreamUrl = uri.toString(); 174 if (isMercurial) { 175 upstreamUrl = "git+" + upstreamUrl; 176 } 177 repo.addRemote("upstream", upstreamUrl); 178 var gitConfig = repo.root().resolve(".git").resolve("config"); 179 if (!isMercurial && Files.exists(gitConfig)) { 180 var lines = List.of( 181 "[sync]", 182 " remote = upstream" 183 ); 184 Files.write(gitConfig, lines, StandardOpenOption.WRITE, StandardOpenOption.APPEND); 185 } 186 System.out.println("done"); 187 } else { 188 System.out.println(webUrl); 189 } 190 } 191 }