golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.98k stars 17.67k forks source link

cmd/go: mitigate merge conflicts when modules are updated #32485

Open vcabbage opened 5 years ago

vcabbage commented 5 years ago

I expect merge conflicts in go.mod and go.sum to be a relatively common occurrence.

Quoting from @bcmills in the Go Slack:

If you have a merge conflict no go.mod or go.sum, you can usually just remove the conflict markers (taking the union of the requirements) and then run go mod tidyto get back into a consistent state.

https://gophers.slack.com/archives/C9BMAAFFB/p1559923453042900?thread_ts=1559922004.041700&cid=C9BMAAFFB

This doesn't appear terribly burdensome but it does introduce friction in the development process. I'm opening this as an exploratory issue to investigate whether there's anything the tooling can/should do to mitigate the conflict.

My off the cuff question was whether go mod tidy could be updated to understand conflicts and automatically resolve them in go.mod and go.sum. My summary of the resulting conversation is that it's likely possible but it's unclear whether it's desirable.

Some of the reasons against that were brought up:

It's possible there's a less invasive approach to mitigating conflicts so I'm including the above suggestion only as one potential option to explore.

bcmills commented 5 years ago

CC @jayconrod

wizardishungry commented 5 years ago

I'd be interested in writing git specific merge driver for go.sum, if anyone else is thinking of the same thing.

bcmills commented 5 years ago

Thinking about this some more... if the area under the merge tokens contains a replace, require, or exclude token, then it isn't correct to just combine the contents. So the merge tool will at least need to know when to call for human intervention.

rbUUbr commented 4 years ago

what about just deleting go.sum and running go mod download?

bcmills commented 4 years ago

@rbUUbr, the point of the go.sum file is to ensure that the checksums do not change once a given version is downloaded. Especially for private dependencies that are not present in the global checksum database, it is important not to discard the originally-recorded checksums.

yobert commented 3 years ago

It seems to me like the algorithm should be: go.sum: Always merge contents together, deduplicating exact row matches go.mod: Whenever conflicting part does not include replace/require/exclude, use MVS algorithm to resolve to the oldest version that satisfies both (in effect, choose the newest version). Any other conflicts, leave be for manual intervention.

Also I think a nice interface would be something like "go mod git-merge" or something like that. It could return an error exit code when it wasn't able to safely auto-merge everything. I think this could be plugged into .git/config so that doing a huge rebase with hundreds of commits would be mostly automatic.

kpruden commented 2 years ago

Hasn't been much movement on this issue, but I thought I'd throw in my vote for this feature.

For what it's worth, I think this would be useful even if it only handled go.sum conflicts and left conflicts in go.mod alone for manual intervention. This ensures a human reviews conflicts in the semantic dependencies (and sidesteps the more tricky conflict scenarios), but lets go mod maintain responsibility for go.sum.

In this case, the workflow would be: resolve conflicts in go.mod then run go mod tidy, which is pretty consistent with other workflows involving manual edits to go.mod..

joelrebel commented 10 months ago

If its of any help, I've added some notes on setting up a git mergetool snippet for this https://gist.github.com/joelrebel/8f73986cb2ec0fb02868e525b1f464c1

ryancurrah commented 8 months ago

I wrote a script to deal with this based on the above merge tool, the script includes running go mod tidy.

I call it modflict. It's meant to be run manually (Not as a git merge tool), put it in your path and run modflict. It will search for all modules in a repository, check for conflicts, remove the conflict markers, and run go mod tidy. If there is an error it will exit non-zero.

#!/usr/bin/env bash
set -e

# changesMadeGlobal flag tracks if any changes were made
changesMadeGlobal=0
# repoRoot points to the repository root
repoRoot="$(git rev-parse --show-toplevel)"

# Search for go.mod files from the repository root
while IFS= read -r goModFile; do
    # Initialize a flag to track if changes were made
    changesMade=0

    # Directory of the go.mod file
    goModDir="$(dirname "${goModFile}")"

    # Corresponding go.sum file
    goSumFile="${goModDir}/go.sum"

    # Function to check for conflict markers and remove them if found
    removeConflictMarkers() {
        local file="${1}"
        # Check if file contains any conflict markers
        if grep -q -E '<<<<<<<|=======|>>>>>>>' "${file}"; then
            echo "Removing conflict markers from ${file}"
            # Remove the conflict markers
            sed -i '/<<<<<<<\|=======\|>>>>>>>/d' "${file}"
            # Indicate that changes have been made
            changesMade=1
            changesMadeGlobal=1
        fi
    }

    # Check and remove conflict markers from go.mod
    removeConflictMarkers "${goModFile}"

    # Check if go.sum exists and remove conflict markers from it
    if [ -f "${goSumFile}" ]; then
        removeConflictMarkers "${goSumFile}"
    fi

    # Run go mod tidy only if changes were made
    if [ "${changesMade}" -eq 1 ]; then
        echo "Running 'go mod tidy' in ${goModDir}"
        (cd "${goModDir}" && go mod tidy)
    fi
# Use process substitution to avoid creating a subshell
done < <(find "${repoRoot}" -type f -name 'go.mod')

if [ "${changesMadeGlobal}" -eq 0 ]; then
    echo "No conflict markers found in go.mod or go.sum files. Exiting..."
fi