💾 Archived View for thrig.me › blog › 2023 › 04 › 29 › git-foo.gmi captured on 2023-06-16 at 16:38:53. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-05-24)

➡️ Next capture (2023-11-14)

-=-=-=-=-=-=-

Git Foo

xkcd://1597

Anyways, an advantage of git (or really any not-so-terrible version control software) is that little test repositories can be created, various commands tried out, and then hopefully suitable commands can be applied to the real repository with lower odds of totally breaking everything. Let's say you create a new repository with the default branch of "green", but at some point changed the branch to the more correct "purple", but then accidentally pushed the latest changes to "green".

    $ mkdir testrepo && cd testrepo
    $ git init
    Initialized empty Git repository in /tmp/testrepo/.git/
    $ git status -sb
    ## No commits yet on green
    $ git config init.defaultbranch
    green

We probably need some commits.

    $ echo foo > bar
    $ git more
    [green (root-commit) 9012529] moremoremore
     1 file changed, 1 insertion(+)
     create mode 100644 bar
    $ echo bar > baz
    $ git more
    [green 2de0ecc] moremoremore
     1 file changed, 1 insertion(+)
     create mode 100644 baz
    $ grep more ~/.gitconfig
      more = "!git add -A; git commit --no-gpg-sign -m moremoremore"

Yes, I have some very strange aliases. Probably aliases like this should not be used, as there are good odds you'll upload all the private keys or a too large coredump. Remember to check the diff before committing? Or don't do commits at 04:00 in the morning after working all those extremely hardcore hours?

Now we switch to the politically correct purple branch, as green is disfavored these days--"green" versus "purple" was the subject of a Babylon 5 episode.

https://www.youtube.com/watch?v=AcBTOU7RvbU

    $ git checkout -b purple
    Switched to a new branch 'purple'
    $ git log | grep purp
    commit 2de0eccc7a7e2451115102d17e8d0ae7ab840fbc (HEAD -> purple, green)

At this point you probably should delete the incorrect branch so you do not accidentally use it. However here we'll put some commits on both of the branches to make fixing the problem more fun--if you consider fixing broken git repositories fun. Apparently normal humans like to hang out at bars... or something like that?

    $ echo purple > foo
    $ git add -A;git commit -m 'foo on purple'
    [purple 1e6947d] foo on purple
     1 file changed, 1 insertion(+)
     create mode 100644 foo
    $ git checkout green
    Switched to branch 'green'
    $ echo green > foo
    $ git add -A;git commit -m 'foo on green'
    [green 7a049d0] foo on green
     1 file changed, 1 insertion(+)
     create mode 100644 foo

There are several ways to fix this, much of which will depend on whether you've already pushed the changes elsewhere, and if so how much grief it would cause any users of your repository.

Brutalism

If we do not care about breaking things, we can simply repoint the good purple branch (remember: purple good, green bad) to the desired changes that were made on the green branch. This assumes the green branch has the changes we want, and that we do not care about any changes made on purple, or any other repositories cloned from this one.

    $ cd ..
    $ cp -r testrepo brutalism
    $ cd brutalism
    $ git status -sb
    ## green
    $ cat foo
    green
    $ git branch -D purple
    Deleted branch purple (was 1e6947d).
    $ git checkout -b purple
    Switched to a new branch 'purple'
    $ git log | grep purple
    commit 7a049d0b0ab02c682d97d4ac7378239679d67fb1 (HEAD -> purple, green)

Since the green branch has the changes we want, we simply remove the purple branch and create it again where the green branch is. Again, if you've already pushed the purple branch somewhere this change may cause grief for anyone who has already pulled that change. What sort of grief? Luckily we still have the original repository, so we can clone that, point it at the brutalism repository, and then try a pull.

    $ cd ..
    $ git clone -l testrepo acopy
    Cloning into 'acopy'...
    done.
    $ cd acopy
    $ git checkout purple
    branch 'purple' set up to track 'origin/purple'.
    Switched to a new branch 'purple'
    $ git config remote.origin.url
    /tmp/testrepo
    $ git config remote.origin.url /tmp/brutalism
    $ git pull
    From /tmp/brutalism
     + 1e6947d...7a049d0 purple     -> origin/purple  (forced update)
    fatal: Not possible to fast-forward, aborting.
    $ git status -sb
    ## purple...origin/purple [ahead 1, behind 1]
    $ git config pull.ff
    only

The result here may depend on the git pull configuration; the essential point is that a user who has cloned the repository now has something that is both ahead and behind, which probably isn't a good user experience and most likely will lead to XKCD 1597: delete the entire repository and clone it. If anything this wastes CPU, network resources, adds wear and tear to the disk...

It looks like --ff-only is the default for a git pull in modern versions of git; in older versions this may not be the case, in which case a pull might do bad things. Probably git should be kept up to date, especially with any security patches. Version 2.40.0 is being used here.

A Better Way?

    $ cd ..
    $ rm -rf acopy brutalism
    $ git clone -l testrepo acopy
    $ cp -r testrepo taketwo
    $ cd taketwo
    $ git status -sb
    ## green
    $ cat foo
    green

Even with a test repository it can help to always work on a copy, especially for when things do not go according to plan. This is similar to doing database changes in a transaction, so you can maybe rollback the changes should things go awry. A new clone of the original is also made so we can test a pull.

Again, the purple branch is good, the green is bad, but we want the green change on the purple branch. "but" in English just means and, from a logical standpoint. And, but with flavor.

    $ git checkout purple
    Switched to branch 'purple'
    $ git rebase green
    Auto-merging foo
    CONFLICT (add/add): Merge conflict in foo
    error: could not apply 1e6947d... foo on purple
    ...
    $ git status -sb
    ## HEAD (no branch)
    AA foo
    $ git diff foo
    diff --cc foo
    index a5b73ed,08ec89e..0000000
    --- foo
    +++ foo
    @@@ -1,1 -1,1 +1,5 @@@
    ++<<<<<<< HEAD
     +green
    ++=======
    + purple
    ++>>>>>>> 1e6947d (foo on purple)

So that fails, because a conflict was created way back when. I almost never have to deal with conflicts, so there might be better diff and merge tools to wrangle conflicts with.

    $ echo green > foo
    $ git rebase --continue
    Successfully rebased and updated refs/heads/purple.

And now we can point the "remote" copy at the updated repository and see how a client pull would go.

    $ cd ../acopy
    $ git checkout purple
    branch 'purple' set up to track 'origin/purple'.
    Switched to a new branch 'purple'
    $ git config remote.origin.url /tmp/taketwo
    $ git pull
    From /tmp/taketwo
     + 1e6947d...7a049d0 purple     -> origin/purple  (forced update)
    fatal: Not possible to fast-forward, aborting.

Nope, still not good for the client. They could probably run something like the following in either this or the brutalism case.

    $ git pull --rebase=true
    Successfully rebased and updated refs/heads/purple.

Another Attempt

    $ cd ..
    $ rm -rf acopy taketwo
    $ git clone -l testrepo acopy
    $ cp -r testrepo third
    $ cd third

Here we will apply the good commit from the bad green branch onto the good purple branch.

    $ git status -sb
    ## green
    $ git log -n 1 | sed 1q
    commit 7a049d0b0ab02c682d97d4ac7378239679d67fb1 (HEAD -> green)
    $ git checkout purple
    Switched to branch 'purple'
    $ git cherry-pick 7a049d0b0ab02c682d97d4ac7378239679d67fb1 --no-commit
    Auto-merging foo
    CONFLICT (add/add): Merge conflict in foo
    ...
    $ echo green > foo
    $ git add foo
    $ commit -m 'apply green changes to purple'
    [purple 263fc79] apply green changes to purple
     1 file changed, 1 insertion(+), 1 deletion(-)

A `git merge green` would probably also work here, as this is a simple case. And now the client repository can pull without problems:

    $ cd ../acopy
    $ git checkout purple
    ...
    $ git config remote.origin.url /tmp/third
    $ git pull
    ...
    Fast-forward
     foo | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)

What Is A Branch?

    $ git branch -a
      green
    * purple
      remotes/origin/HEAD -> origin/green
      remotes/origin/green
      remotes/origin/purple
    $ git log -n 1 | sed 1q
    commit 263fc79ba05fa1d04d1a37bbb8cd015dd0bced6d (HEAD -> purple, origin/purple)
    $ git update-ref refs/heads/yellow 263fc79ba05fa1d04d1a37bbb8cd015dd0bced6d
    $ git symbolic-ref HEAD refs/heads/yellow
    $ git status -sb
    ## yellow
    $ git branch -a
      green
      purple
    * yellow
      remotes/origin/HEAD -> origin/green
      remotes/origin/green
      remotes/origin/purple

Homework

Yay! Homework!! For more education, you should probably work through

https://github.com/jwiegley/git-from-the-bottom-up

or switch to some other version control software. I'm mostly using git because it's popular and a sysadmin might well be asked to fix a broken git repository, more likely to answer simple git questions. Developers might need something else from their version control software? The author of fossil seems to think so.

There is also the git book, which I probably need to read more of one of these years.

https://git-scm.com/book/en/v2

tags #git

bphflog links

bphflog index

next: Names II