mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-28 02:07:09 -04:00
Store each branch's push destination in the branch model
To push a branch other than the checked-out one, lazygit has to name the remote and the remote branch in the push command; a bare `git push` only works for the current branch. The upstream isn't always the right destination. In a triangular workflow the branch is pushed to a different remote than it pulls from, and with push.default set to "current" it goes to a branch of the same name whatever the upstream is called. Read the destination from git instead of working it out from the config. The %(push) field of for-each-ref names the remote-tracking ref that a push would update, taking push.default, remote.pushDefault and branch.<name>.pushRemote into account. It is also the ref that the push:track counts are computed against, so the force-push detection and the destination agree. The field is available since git 2.5, well before the oldest version lazygit supports. Nothing uses the new fields yet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
58fe972a14
commit
fa427c32b3
@@ -349,6 +349,7 @@ var branchFields = []string{
|
||||
"upstream:short",
|
||||
"upstream:track",
|
||||
"push:track",
|
||||
"push",
|
||||
"subject",
|
||||
"objectname",
|
||||
"committerdate:unix",
|
||||
@@ -361,13 +362,15 @@ func obtainBranch(split []string, storeCommitDateAsRecency bool) (*models.Branch
|
||||
upstreamName := split[2]
|
||||
track := split[3]
|
||||
pushTrack := split[4]
|
||||
subject := split[5]
|
||||
commitHash := split[6]
|
||||
commitDate := split[7]
|
||||
pushRef := split[5]
|
||||
subject := split[6]
|
||||
commitHash := split[7]
|
||||
commitDate := split[8]
|
||||
|
||||
name := strings.TrimPrefix(fullName, "heads/")
|
||||
aheadForPull, behindForPull, gone := parseUpstreamInfo(upstreamName, track)
|
||||
aheadForPush, behindForPush, _ := parseUpstreamInfo(upstreamName, pushTrack)
|
||||
pushRemote, pushBranch := parsePushDestination(pushRef)
|
||||
|
||||
recency := ""
|
||||
if storeCommitDateAsRecency {
|
||||
@@ -383,6 +386,8 @@ func obtainBranch(split []string, storeCommitDateAsRecency bool) (*models.Branch
|
||||
BehindForPull: behindForPull,
|
||||
AheadForPush: aheadForPush,
|
||||
BehindForPush: behindForPush,
|
||||
PushRemote: pushRemote,
|
||||
PushBranch: pushBranch,
|
||||
UpstreamGone: gone,
|
||||
Head: headMarker == "*",
|
||||
Subject: subject,
|
||||
@@ -410,6 +415,25 @@ func parseUpstreamInfo(upstreamName string, track string) (string, string, bool)
|
||||
return ahead, behind, false
|
||||
}
|
||||
|
||||
// Splits the remote-tracking ref that the %(push) field names, e.g.
|
||||
// refs/remotes/origin/main, into the remote and the remote branch. Returns
|
||||
// empty strings if the field is empty because git has no push destination for
|
||||
// the branch, or if the ref isn't under refs/remotes/.
|
||||
func parsePushDestination(pushRef string) (string, string) {
|
||||
remoteAndBranch, ok := strings.CutPrefix(pushRef, "refs/remotes/")
|
||||
if !ok {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// Remote names can't contain slashes, so the first one ends the remote name
|
||||
remote, branch, ok := strings.Cut(remoteAndBranch, "/")
|
||||
if !ok {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
return remote, branch
|
||||
}
|
||||
|
||||
func parseDifference(track string, regexStr string) string {
|
||||
re := regexp.MustCompile(regexStr)
|
||||
match := re.FindStringSubmatch(track)
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestObtainBranch(t *testing.T) {
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "TrimHeads",
|
||||
input: []string{"", "heads/a_branch", "", "", "", "subject", "123", timeStamp},
|
||||
input: []string{"", "heads/a_branch", "", "", "", "", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: false,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
@@ -41,7 +41,7 @@ func TestObtainBranch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "NoUpstream",
|
||||
input: []string{"", "a_branch", "", "", "", "subject", "123", timeStamp},
|
||||
input: []string{"", "a_branch", "", "", "", "", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: false,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
@@ -56,7 +56,7 @@ func TestObtainBranch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "IsHead",
|
||||
input: []string{"*", "a_branch", "", "", "", "subject", "123", timeStamp},
|
||||
input: []string{"*", "a_branch", "", "", "", "", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: false,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
@@ -71,7 +71,7 @@ func TestObtainBranch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "IsBehindAndAhead",
|
||||
input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "[behind 2, ahead 3]", "subject", "123", timeStamp},
|
||||
input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "[behind 2, ahead 3]", "refs/remotes/a_remote/a_branch", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: false,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
@@ -79,6 +79,40 @@ func TestObtainBranch(t *testing.T) {
|
||||
BehindForPull: "2",
|
||||
AheadForPush: "3",
|
||||
BehindForPush: "2",
|
||||
PushRemote: "a_remote",
|
||||
PushBranch: "a_branch",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "PushDestinationDiffersFromUpstream",
|
||||
input: []string{"", "a_branch", "a_remote/a_branch", "[ahead 3]", "[ahead 5]", "refs/remotes/my_fork/feature/a_branch", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: false,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
AheadForPull: "3",
|
||||
BehindForPull: "0",
|
||||
AheadForPush: "5",
|
||||
BehindForPush: "0",
|
||||
PushRemote: "my_fork",
|
||||
PushBranch: "feature/a_branch",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "PushDestinationNotARemoteTrackingRef",
|
||||
input: []string{"", "a_branch", "a_remote/a_branch", "", "", "refs/published/a_branch", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: false,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
AheadForPull: "0",
|
||||
BehindForPull: "0",
|
||||
AheadForPush: "0",
|
||||
BehindForPush: "0",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
@@ -86,7 +120,7 @@ func TestObtainBranch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "RemoteBranchIsGone",
|
||||
input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "[gone]", "subject", "123", timeStamp},
|
||||
input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "[gone]", "refs/remotes/a_remote/a_branch", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: false,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
@@ -95,6 +129,8 @@ func TestObtainBranch(t *testing.T) {
|
||||
BehindForPull: "?",
|
||||
AheadForPush: "?",
|
||||
BehindForPush: "?",
|
||||
PushRemote: "a_remote",
|
||||
PushBranch: "a_branch",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
@@ -102,7 +138,7 @@ func TestObtainBranch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "WithCommitDateAsRecency",
|
||||
input: []string{"", "a_branch", "", "", "", "subject", "123", timeStamp},
|
||||
input: []string{"", "a_branch", "", "", "", "", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: true,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
|
||||
@@ -31,6 +31,13 @@ type Branch struct {
|
||||
// 'git@github.com:tiwood/lazygit.git'
|
||||
UpstreamRemote string
|
||||
UpstreamBranch string
|
||||
// The remote and the remote branch that `git push` would push this branch
|
||||
// to, as git determines them from push.default, remote.pushDefault and
|
||||
// branch.<name>.pushRemote. In a triangular workflow these differ from the
|
||||
// upstream. Both are empty if git has no push destination for the branch,
|
||||
// e.g. because push.default is "upstream" and the branch has no upstream.
|
||||
PushRemote string
|
||||
PushBranch string
|
||||
// subject line in commit message
|
||||
Subject string
|
||||
// commit hash
|
||||
|
||||
Reference in New Issue
Block a user