Support custom branches (#236)
This commit is contained in:
parent
744b46adc2
commit
6b4dbd50a9
6 changed files with 81 additions and 15 deletions
|
|
@ -10,6 +10,7 @@ import UIKit
|
||||||
import CoreData
|
import CoreData
|
||||||
import SVProgressHUD
|
import SVProgressHUD
|
||||||
import passKit
|
import passKit
|
||||||
|
import SwiftyUserDefaults
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
@ -33,6 +34,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
self.perform(#selector(postSearchNotification), with: nil, afterDelay: 0.4)
|
self.perform(#selector(postSearchNotification), with: nil, afterDelay: 0.4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assign default values to global settings.
|
||||||
|
SharedDefaults.register(defaults: [DefaultsKeys.gitBranchName._key: "master"])
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -381,10 +381,39 @@
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
</cells>
|
</cells>
|
||||||
</tableViewSection>
|
</tableViewSection>
|
||||||
|
<tableViewSection headerTitle="Branch Name" id="Uoy-58-5ug" userLabel="Branch Name">
|
||||||
|
<cells>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="5XL-Uj-JWL" userLabel="branchNameTableViewCell">
|
||||||
|
<rect key="frame" x="0.0" y="271.33333333333337" width="414" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="5XL-Uj-JWL" id="BfY-mD-gfv">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="43.666666666666664"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Branch Name" textAlignment="natural" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="VVI-gJ-e37" userLabel="Text">
|
||||||
|
<rect key="frame" x="20" y="6.6666666666666661" width="374" height="30.333333333333336"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||||
|
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet" returnKeyType="done"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="delegate" destination="ynQ-64-MfA" id="y89-Ub-ecB"/>
|
||||||
|
</connections>
|
||||||
|
</textField>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="VVI-gJ-e37" firstAttribute="trailing" secondItem="BfY-mD-gfv" secondAttribute="trailingMargin" id="IGP-5Y-nMm"/>
|
||||||
|
<constraint firstItem="VVI-gJ-e37" firstAttribute="centerY" secondItem="BfY-mD-gfv" secondAttribute="centerY" id="IOt-p9-Bwe"/>
|
||||||
|
<constraint firstItem="VVI-gJ-e37" firstAttribute="leading" secondItem="BfY-mD-gfv" secondAttribute="leadingMargin" id="alH-v2-xcf"/>
|
||||||
|
<constraint firstAttribute="bottomMargin" secondItem="VVI-gJ-e37" secondAttribute="bottom" constant="-4" id="pAT-la-hAA"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
</tableViewCell>
|
||||||
|
</cells>
|
||||||
|
</tableViewSection>
|
||||||
<tableViewSection headerTitle="Authentication Method" id="h0N-tI-shZ">
|
<tableViewSection headerTitle="Authentication Method" id="h0N-tI-shZ">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="2" indentationWidth="0.0" shouldIndentWhileEditing="NO" id="KrP-nb-haa">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="2" indentationWidth="0.0" shouldIndentWhileEditing="NO" id="KrP-nb-haa">
|
||||||
<rect key="frame" x="0.0" y="271.33333333333337" width="414" height="44"/>
|
<rect key="frame" x="0.0" y="371.33333333333337" width="414" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KrP-nb-haa" id="1uB-oE-kfI">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KrP-nb-haa" id="1uB-oE-kfI">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="43.666666666666664"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="43.666666666666664"/>
|
||||||
|
|
@ -416,7 +445,7 @@
|
||||||
<inset key="separatorInset" minX="62" minY="0.0" maxX="0.0" maxY="0.0"/>
|
<inset key="separatorInset" minX="62" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="blue" accessoryType="detailButton" hidesAccessoryWhenEditing="NO" indentationLevel="2" indentationWidth="0.0" shouldIndentWhileEditing="NO" id="Qmt-bo-CuJ">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="blue" accessoryType="detailButton" hidesAccessoryWhenEditing="NO" indentationLevel="2" indentationWidth="0.0" shouldIndentWhileEditing="NO" id="Qmt-bo-CuJ">
|
||||||
<rect key="frame" x="0.0" y="315.33333333333337" width="414" height="44"/>
|
<rect key="frame" x="0.0" y="415.33333333333337" width="414" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Qmt-bo-CuJ" id="p3u-8b-h3U">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Qmt-bo-CuJ" id="p3u-8b-h3U">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="362" height="43.666666666666664"/>
|
<rect key="frame" x="0.0" y="0.0" width="362" height="43.666666666666664"/>
|
||||||
|
|
@ -466,6 +495,7 @@
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="authPasswordCell" destination="KrP-nb-haa" id="zjH-sg-dfJ"/>
|
<outlet property="authPasswordCell" destination="KrP-nb-haa" id="zjH-sg-dfJ"/>
|
||||||
<outlet property="authSSHKeyCell" destination="Qmt-bo-CuJ" id="11L-Tt-MgM"/>
|
<outlet property="authSSHKeyCell" destination="Qmt-bo-CuJ" id="11L-Tt-MgM"/>
|
||||||
|
<outlet property="branchNameTextField" destination="VVI-gJ-e37" id="XLA-8I-yPm"/>
|
||||||
<outlet property="gitURLTextField" destination="EVT-VU-sCi" id="XdU-3l-Nsv"/>
|
<outlet property="gitURLTextField" destination="EVT-VU-sCi" id="XdU-3l-Nsv"/>
|
||||||
<outlet property="usernameTextField" destination="TMg-Gk-7nG" id="htL-4C-WJF"/>
|
<outlet property="usernameTextField" destination="TMg-Gk-7nG" id="htL-4C-WJF"/>
|
||||||
<segue destination="7K9-cE-9qq" kind="unwind" identifier="saveGitServerSettingSegue" unwindAction="saveGitServerSettingWithSegue:" id="5UN-sC-xCA"/>
|
<segue destination="7K9-cE-9qq" kind="unwind" identifier="saveGitServerSettingSegue" unwindAction="saveGitServerSettingWithSegue:" id="5UN-sC-xCA"/>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
|
|
||||||
@IBOutlet weak var gitURLTextField: UITextField!
|
@IBOutlet weak var gitURLTextField: UITextField!
|
||||||
@IBOutlet weak var usernameTextField: UITextField!
|
@IBOutlet weak var usernameTextField: UITextField!
|
||||||
|
@IBOutlet weak var branchNameTextField: UITextField!
|
||||||
@IBOutlet weak var authSSHKeyCell: UITableViewCell!
|
@IBOutlet weak var authSSHKeyCell: UITableViewCell!
|
||||||
@IBOutlet weak var authPasswordCell: UITableViewCell!
|
@IBOutlet weak var authPasswordCell: UITableViewCell!
|
||||||
let passwordStore = PasswordStore.shared
|
let passwordStore = PasswordStore.shared
|
||||||
|
|
@ -51,6 +52,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
gitURLTextField.text = url.absoluteString
|
gitURLTextField.text = url.absoluteString
|
||||||
}
|
}
|
||||||
usernameTextField.text = SharedDefaults[.gitUsername]
|
usernameTextField.text = SharedDefaults[.gitUsername]
|
||||||
|
branchNameTextField.text = SharedDefaults[.gitBranchName]
|
||||||
sshLabel = authSSHKeyCell.subviews[0].subviews[0] as? UILabel
|
sshLabel = authSSHKeyCell.subviews[0].subviews[0] as? UILabel
|
||||||
checkAuthenticationMethod(method: authenticationMethod)
|
checkAuthenticationMethod(method: authenticationMethod)
|
||||||
authSSHKeyCell.accessoryType = .detailButton
|
authSSHKeyCell.accessoryType = .detailButton
|
||||||
|
|
@ -77,6 +79,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
// try to clone
|
// try to clone
|
||||||
let gitRepostiroyURL = gitURLTextField.text!.trimmed
|
let gitRepostiroyURL = gitURLTextField.text!.trimmed
|
||||||
let username = usernameTextField.text!
|
let username = usernameTextField.text!
|
||||||
|
let branchName = branchNameTextField.text!
|
||||||
let auth = authenticationMethod
|
let auth = authenticationMethod
|
||||||
|
|
||||||
SVProgressHUD.setDefaultMaskType(.black)
|
SVProgressHUD.setDefaultMaskType(.black)
|
||||||
|
|
@ -100,6 +103,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
do {
|
do {
|
||||||
try self.passwordStore.cloneRepository(remoteRepoURL: URL(string: gitRepostiroyURL)!,
|
try self.passwordStore.cloneRepository(remoteRepoURL: URL(string: gitRepostiroyURL)!,
|
||||||
credential: gitCredential,
|
credential: gitCredential,
|
||||||
|
branchName: branchName,
|
||||||
requestGitPassword: self.requestGitPassword,
|
requestGitPassword: self.requestGitPassword,
|
||||||
transferProgressBlock: { (git_transfer_progress, stop) in
|
transferProgressBlock: { (git_transfer_progress, stop) in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|
@ -114,6 +118,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
SharedDefaults[.gitURL] = URL(string: gitRepostiroyURL)
|
SharedDefaults[.gitURL] = URL(string: gitRepostiroyURL)
|
||||||
SharedDefaults[.gitUsername] = username
|
SharedDefaults[.gitUsername] = username
|
||||||
|
SharedDefaults[.gitBranchName] = branchName
|
||||||
SharedDefaults[.gitAuthenticationMethod] = auth
|
SharedDefaults[.gitAuthenticationMethod] = auth
|
||||||
SVProgressHUD.dismiss()
|
SVProgressHUD.dismiss()
|
||||||
let savePassphraseAlert = UIAlertController(title: "Done", message: "Do you want to save the Git credential password/passphrase?", preferredStyle: UIAlertControllerStyle.alert)
|
let savePassphraseAlert = UIAlertController(title: "Done", message: "Do you want to save the Git credential password/passphrase?", preferredStyle: UIAlertControllerStyle.alert)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import Foundation
|
||||||
|
|
||||||
public enum AppError: Error {
|
public enum AppError: Error {
|
||||||
case RepositoryNotSetError
|
case RepositoryNotSetError
|
||||||
case RepositoryRemoteMasterNotFoundError
|
case RepositoryRemoteBranchNotFoundError(_: String)
|
||||||
|
case RepositoryBranchNotFound(_: String)
|
||||||
case KeyImportError
|
case KeyImportError
|
||||||
case PasswordDuplicatedError
|
case PasswordDuplicatedError
|
||||||
case GitResetError
|
case GitResetError
|
||||||
|
|
@ -25,8 +26,10 @@ extension AppError: LocalizedError {
|
||||||
switch self {
|
switch self {
|
||||||
case .RepositoryNotSetError:
|
case .RepositoryNotSetError:
|
||||||
return "Git repository is not set."
|
return "Git repository is not set."
|
||||||
case .RepositoryRemoteMasterNotFoundError:
|
case let .RepositoryRemoteBranchNotFoundError(remoteBranchName):
|
||||||
return "Cannot find remote branch origin/master."
|
return "Cannot find remote branch 'origin/\(remoteBranchName)'."
|
||||||
|
case let .RepositoryBranchNotFound(branchName):
|
||||||
|
return "Branch with name '\(branchName)' not found in repository."
|
||||||
case .KeyImportError:
|
case .KeyImportError:
|
||||||
return "Cannot import the key."
|
return "Cannot import the key."
|
||||||
case .PasswordDuplicatedError:
|
case .PasswordDuplicatedError:
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ public extension DefaultsKeys {
|
||||||
static let gitURL = DefaultsKey<URL?>("gitURL")
|
static let gitURL = DefaultsKey<URL?>("gitURL")
|
||||||
static let gitAuthenticationMethod = DefaultsKey<String?>("gitAuthenticationMethod")
|
static let gitAuthenticationMethod = DefaultsKey<String?>("gitAuthenticationMethod")
|
||||||
static let gitUsername = DefaultsKey<String?>("gitUsername")
|
static let gitUsername = DefaultsKey<String?>("gitUsername")
|
||||||
|
static let gitBranchName = DefaultsKey<String?>("gitBranchName")
|
||||||
static let gitSSHPrivateKeyURL = DefaultsKey<URL?>("gitSSHPrivateKeyURL")
|
static let gitSSHPrivateKeyURL = DefaultsKey<URL?>("gitSSHPrivateKeyURL")
|
||||||
static let gitSSHKeySource = DefaultsKey<String?>("gitSSHKeySource")
|
static let gitSSHKeySource = DefaultsKey<String?>("gitSSHKeySource")
|
||||||
static let gitSSHPrivateKeyArmor = DefaultsKey<String?>("gitSSHPrivateKeyArmor")
|
static let gitSSHPrivateKeyArmor = DefaultsKey<String?>("gitSSHPrivateKeyArmor")
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,7 @@ public class PasswordStore {
|
||||||
|
|
||||||
public func cloneRepository(remoteRepoURL: URL,
|
public func cloneRepository(remoteRepoURL: URL,
|
||||||
credential: GitCredential,
|
credential: GitCredential,
|
||||||
|
branchName: String,
|
||||||
requestGitPassword: @escaping (GitCredential.Credential, String?) -> String?,
|
requestGitPassword: @escaping (GitCredential.Credential, String?) -> String?,
|
||||||
transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void,
|
transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void,
|
||||||
checkoutProgressBlock: @escaping (String?, UInt, UInt) -> Void) throws {
|
checkoutProgressBlock: @escaping (String?, UInt, UInt) -> Void) throws {
|
||||||
|
|
@ -312,6 +313,7 @@ public class PasswordStore {
|
||||||
storeRepository = try GTRepository.clone(from: remoteRepoURL, toWorkingDirectory: tempStoreURL, options: options, transferProgressBlock:transferProgressBlock)
|
storeRepository = try GTRepository.clone(from: remoteRepoURL, toWorkingDirectory: tempStoreURL, options: options, transferProgressBlock:transferProgressBlock)
|
||||||
try fm.moveItem(at: tempStoreURL, to: storeURL)
|
try fm.moveItem(at: tempStoreURL, to: storeURL)
|
||||||
storeRepository = try GTRepository(url: storeURL)
|
storeRepository = try GTRepository(url: storeURL)
|
||||||
|
try checkoutAndChangeBranch(withName: branchName)
|
||||||
} catch {
|
} catch {
|
||||||
credential.delete()
|
credential.delete()
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|
@ -328,6 +330,27 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func checkoutAndChangeBranch(withName localBranchName: String) throws {
|
||||||
|
if (localBranchName == "master") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let storeRepository = storeRepository else {
|
||||||
|
throw AppError.RepositoryNotSetError
|
||||||
|
}
|
||||||
|
let remoteBranchName = "origin/\(localBranchName)"
|
||||||
|
guard let remoteBranch = try? storeRepository.lookUpBranch(withName: remoteBranchName, type: .remote, success: nil) else {
|
||||||
|
throw AppError.RepositoryRemoteBranchNotFoundError(remoteBranchName)
|
||||||
|
}
|
||||||
|
guard let remoteBranchOid = remoteBranch.oid else {
|
||||||
|
throw AppError.RepositoryRemoteBranchNotFoundError(remoteBranchName)
|
||||||
|
}
|
||||||
|
let localBranch = try storeRepository.createBranchNamed(localBranchName, from: remoteBranchOid, message: nil)
|
||||||
|
try localBranch.updateTrackingBranch(remoteBranch)
|
||||||
|
let checkoutOptions = GTCheckoutOptions.init(strategy: .force)
|
||||||
|
try storeRepository.checkoutReference(localBranch.reference, options: checkoutOptions)
|
||||||
|
try storeRepository.moveHEAD(to: localBranch.reference)
|
||||||
|
}
|
||||||
|
|
||||||
public func pullRepository(credential: GitCredential, requestGitPassword: @escaping (GitCredential.Credential, String?) -> String?, transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
public func pullRepository(credential: GitCredential, requestGitPassword: @escaping (GitCredential.Credential, String?) -> String?, transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||||
guard let storeRepository = storeRepository else {
|
guard let storeRepository = storeRepository else {
|
||||||
throw AppError.RepositoryNotSetError
|
throw AppError.RepositoryNotSetError
|
||||||
|
|
@ -567,9 +590,9 @@ public class PasswordStore {
|
||||||
do {
|
do {
|
||||||
let credentialProvider = try credential.credentialProvider(requestGitPassword: requestGitPassword)
|
let credentialProvider = try credential.credentialProvider(requestGitPassword: requestGitPassword)
|
||||||
let options = [GTRepositoryRemoteOptionsCredentialProvider: credentialProvider]
|
let options = [GTRepositoryRemoteOptionsCredentialProvider: credentialProvider]
|
||||||
if let masterBranch = try getLocalBranch(withName: "master") {
|
if let branch = try getLocalBranch(withName: SharedDefaults[.gitBranchName]!) {
|
||||||
let remote = try GTRemote(name: "origin", in: storeRepository)
|
let remote = try GTRemote(name: "origin", in: storeRepository)
|
||||||
try storeRepository.push(masterBranch, to: remote, withOptions: options, progress: transferProgressBlock)
|
try storeRepository.push(branch, to: remote, withOptions: options, progress: transferProgressBlock)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
throw(error)
|
throw(error)
|
||||||
|
|
@ -790,19 +813,18 @@ public class PasswordStore {
|
||||||
guard let storeRepository = storeRepository else {
|
guard let storeRepository = storeRepository else {
|
||||||
throw AppError.RepositoryNotSetError
|
throw AppError.RepositoryNotSetError
|
||||||
}
|
}
|
||||||
// get the remote origin/master branch
|
// get the remote branch
|
||||||
guard let index = try storeRepository.remoteBranches().index(where: { $0.shortName == "master" }) else {
|
let remoteBranchName = SharedDefaults[.gitBranchName]!
|
||||||
throw AppError.RepositoryRemoteMasterNotFoundError
|
guard let remoteBranch = try storeRepository.remoteBranches().first(where: { $0.shortName == remoteBranchName }) else {
|
||||||
|
throw AppError.RepositoryRemoteBranchNotFoundError(remoteBranchName)
|
||||||
}
|
}
|
||||||
let remoteMasterBranch = try storeRepository.remoteBranches()[index]
|
|
||||||
|
|
||||||
// check oid before calling localCommitsRelative
|
// check oid before calling localCommitsRelative
|
||||||
guard remoteMasterBranch.oid != nil else {
|
guard remoteBranch.oid != nil else {
|
||||||
throw AppError.RepositoryRemoteMasterNotFoundError
|
throw AppError.RepositoryRemoteBranchNotFoundError(remoteBranchName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get a list of local commits
|
// get a list of local commits
|
||||||
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteMasterBranch)
|
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue