Git is amazing! Or Not!!



  • @dkf said in Git is amazing! Or Not!!:

    @Bulb said in Git is amazing! Or Not!!:

    So when you got a conflict, you just do the same thing, just with better understanding. Usually each side changed different argument to a function or something, so you just do the two changes. Don't think what they mean unless the code was completely kicked apart and reassembled.

    And when the code really doesn't match up nicely, you need to understand the changes on each side to know how to reapply them. For example,. if one side is doing an internal API rewrite and the other is adding safety code, chances are you should accept the API changes and redo the safety code on top of that.

    Of course. There are the cases where the code is kicked up too much to textually combine the changes and you have to understand them and redo them on the changed code. And then there are the cases where the conflict is semantic. Which the version control may not even notice happened—if one branch changes a function signature, and the other adds more calls using the old signature, the merge will happily go through and then build will fail. Or it will die in production if it is a duck-typed language like python or javascript.

    The bits I dislike are when the diff algorithm gets hung up on minor details and ends up effectively saying that the whole file is a colliding change;

    The patience diff helped quite a bit with this, but it still happens when one side does something like reformatting the file. Reformatting is PainInTheArse™. Fortunately kdiff3 can ignore whitespace changes which helps quite a bit. Otherwise you merge to before the reformatting, then merge the formatting change with strategy ours, apply the same formatting and hope there are no other changes in that commit, then you proceed to merge the rest.

    the merge algorithm is only as good as the diff generation algorithm's inputs. That's when it would be better if the differ knew the structure of what it was diffing (e.g., if you have that, you can sensibly do small changes to JSON, YAML and XML files).

    You can configure format (well, file extension or pattern)-specific diff and merge helpers in git. There is, unfortunately, shortage of good ones.

    Some time ago I even started on making one for the gettext po files. It kind of worked, but I never really finished it.



  • @dkf I’ve read the docs but I still don’t understand what force commit is supposed to do? It says it overwrites the commit history. So you just lose the history then? What’s the big deal? I’m pretty sure I’m missing something but I don’t know what it is.



  • @stillwater … neither do I, because there is no such (--force) option in svn commit.

    And doing a force push to git wouldn't actually be that much of a problem (most of the time), because when whoever pushed the last version there does pull, they'd get merge of the forced revision with the version they pushed previously, and then they'd push the correct result back.


  • BINNED

    @stillwater said in Git is amazing! Or Not!!:

    @dkf I’ve read the docs but I still don’t understand what force commit is supposed to do? It says it overwrites the commit history. So you just lose the history then? What’s the big deal? I’m pretty sure I’m missing something but I don’t know what it is.

    Consider the following:
    You have a common commit history up to point A.
    Your coworker commits (and pushes to the shared upstream) a new change B, so the new history (for him locally and for upstream) is A->B.
    Meanwhile, you make a different change C to A, before fetching your coworkers changes. Your local history looks like A->C. You can now do different things to merge that together: rebase (A->B->C*), fetch your coworkers changes and do a merge commit, make an actual branch and just push that...

    What force push does, instead, is say "fuck my coworker, and fuck his commits, the new history is 'A->C' and B goes to the trash-can."


  • Discourse touched me in a no-no place

    @stillwater said in Git is amazing! Or Not!!:

    I still don’t understand what force commit is supposed to do?

    It says make the state I've got checked out be the next state of the repo (on the current branch). It's necessary when you're undoing a fuck-up, but normally you want to be notified when the change you're trying to do isn't exactly what you expect so you can avoid wiping out someone else's work or forking the branch by accident. Having force commit always set means you spend your life wiping out others' work, even if you're doing something largely unrelated.

    In my current project (which is git) force-commits are disabled on principal branches by policy. On those rare occasions where I need to fix a fuck-up, I have to explicitly take the safeties off first...


  • Discourse touched me in a no-no place

    @topspin said in Git is amazing! Or Not!!:

    What force push does, instead, is say "fuck my coworker, and fuck his commits, the new history is 'A->C' and B goes to the trash-can."

    You can recover the changes out of the history, but it's deeply annoying to do. It's much more annoying if you have to do this several times in a row.



  • @topspin So the changes in commit B is lost forever except on the coworker’s local machine?



  • @Kamil-Podlesak said in Git is amazing! Or Not!!:

    Yes, sotfware developers are legally people, I have checked

    [citation needed]

    Ok, maybe they're legally people, like corporations are legally people, but the difference between a legal person and a real person is obvious if you've ever dealt with one.


  • Discourse touched me in a no-no place

    @stillwater said in Git is amazing! Or Not!!:

    @topspin So the changes in commit B is lost forever except on the coworker’s local machine?

    Not in subversion, and only in git once the system garbage collects.



  • @dkf What is a legit use case for a force push then?


  • BINNED

    @stillwater said in Git is amazing! Or Not!!:

    @dkf What is a legit use case for a force push then?

    Removing private keys from the repository. (❓)


  • Discourse touched me in a no-no place

    @stillwater The legit use is when you're fixing something where stuff has gone badly wrong. (It's easier to go wrong in git, but it is easier to fix too.) There's a related use where you get the repo to forget a change entirely (typically only used when someone is dumb enough to commit live credentials or material that's outright illegal, or when someone decides that committing 200MB binary output files is a good plan) but the use of that should be even rarer than for simple forces.


  • 🚽 Regular

    @topspin said in Git is amazing! Or Not!!:

    @stillwater said in Git is amazing! Or Not!!:

    @dkf What is a legit use case for a force push then?

    Removing private keys from the repository. (❓)

    Or anything else you don't want there, like large binaries files you've added by mistake and which will only waste space and bandwidth.

    I'm not 100% sure if git downloads all files of all new commits whenever there's a new pull, and :kneeling_warthog: to check, but I think so.


  • Discourse touched me in a no-no place

    @Zecc said in Git is amazing! Or Not!!:

    I'm not 100% sure if git downloads all files of all new commits whenever there's a new pull

    Usually yes. You can tell it not to, but then you're typically not going to use that clone for normal development (but rather for running tests in a CI environment or something like that).



  • @dkf said in Git is amazing! Or Not!!:

    In my current project (which is git) force-commits are disabled on principal branches by policy. On those rare occasions where I need to fix a fuck-up, I have to explicitly take the safeties off first...

    The only time I've ever attempted to do a force push was when several GB of artifacts that should never have been in version control inadvertently got checked in. You can "delete" those files, so they don't get pulled by users, but they're still inflating the repo size, and they still get cloned with the repo. The only way to permanently delete them is to force push the version from before that check-in. At my boss's request, I attempted to do that, but I didn't have the required privilege, and he never got around to giving it to me. And the project moved on... AFAIK, those files are in the repo to this day.



  • @dkf said in Git is amazing! Or Not!!:

    @topspin said in Git is amazing! Or Not!!:

    What force push does, instead, is say "fuck my coworker, and fuck his commits, the new history is 'A->C' and B goes to the trash-can."

    You can recover the changes out of the history, but it's deeply annoying to do. It's much more annoying if you have to do this several times in a row.

    What happens in Subversion and what happens in Git is very different in this case.

    In Subversion the client does not have any way to overwrite the previous revision (there are tools to fiddle with the database server-side, but not through the protocol), so the “forced” commit will appear as a revert. And you'll have to notice it and revert it.

    In contrast in Git it will discard the old history on the server, and replace it with the new one. But it will not do it on the machine of the developer who pushed the previous version. So when that developer does a pull, the old history they have locally, and the new history that was force pushed, will automatically get merged. And if that developer does changes on separate branches, they'll expect the pull to be “fast forward” and it won't be, which will tip them off that a rewrite happened. And then they'll push and that will fix the problem.

    Now that is of course why you shouldn't do force-pushes to shared branches (to feature branches they are generally fine, and often done to clean up various dead-end experiments to make review easier). The next person pushing to the branch will, if they don't expect this, merge the old and new history, getting the old history back in. So if you are doing it to fix a fuck-up, you absolutely must instruct everybody to reset their branches appropriately. Which is actually… quite hard.


  • Discourse touched me in a no-no place

    @HardwareGeek The fix, if it can be done (it's extremely difficult to do this to svn), is to declare that commit to be on a branch and that the main history goes on without them. Then you can purge the branch from the server and only the broken commits get snipped out.

    Purging a bad file/commit in svn is nightmarish, involving basically stopping the service entirely while you dump the entire commit database and restore it after running through a filter.



  • @dkf Indeed. The point is that, because it requires editing the database server-side, the moron couldn't actually delete the information by forcing commits, though he could make you lose a lot of time searching for the right version to revert to.



  • @dkf There was some VCS that I've used — I don't remember which one, and :kneeling_warthog: to look it up — that had an "obliterate" command for this purpose, make the repo forget this file ever existed. I never had a reason to use it, though.

    Fake edit: Come to think of it, it was probably Perforce, but still :kneeling_warthog:.


  • ♿ (Parody)

    @Bulb said in Git is amazing! Or Not!!:

    Or it will die in production if it is a duck-typed language like python or javascript.

    Don't test thread is :arrows:


  • ♿ (Parody)

    @topspin said in Git is amazing! Or Not!!:

    You have a common commit history up to point A.
    Your coworker commits (and pushes to the shared upstream) a new change B, so the new history (for him locally and for upstream) is A->B.

    An observation. A -> B (parent -> child) is how I've always thought about these graphs. But someone posted a bunch of Raymon Chen links about git merging and he does the opposite: A <- B (parent <- child). It really confused me for a bit when I started reading.


  • BINNED

    @boomzilla considering it’s Raymond, the difference is probably that he actually thought about it and has a reason for it, while I have not. :mlp_shrug:



  • @HardwareGeek said in Git is amazing! Or Not!!:

    @dkf There was some VCS that I've used — I don't remember which one, and :kneeling_warthog: to look it up — that had an "obliterate" command for this purpose, make the repo forget this file ever existed. I never had a reason to use it, though.

    Fake edit: Come to think of it, it was probably Perforce, but still :kneeling_warthog:.

    Yes, Perforce. I've actually used it. In the n years I used Perforce, I think I only did it twice - both times to removes 💩 that shouldn't have been checked in. (Stop checking in the Visual Studio intellisense database asshole!)



  • @boomzilla said in Git is amazing! Or Not!!:

    @topspin said in Git is amazing! Or Not!!:

    You have a common commit history up to point A.
    Your coworker commits (and pushes to the shared upstream) a new change B, so the new history (for him locally and for upstream) is A->B.

    An observation. A -> B (parent -> child) is how I've always thought about these graphs. But someone posted a bunch of Raymon Chen links about git merging and he does the opposite: A <- B (parent <- child). It really confused me for a bit when I started reading.

    Well, the parent ← child notation makes a bit more sense because it is the child that has a pointer to the parent while the parent is already etched in stone and completely oblivious to any children created. It also matches most UML diagrams.


  • ♿ (Parody)

    @topspin said in Git is amazing! Or Not!!:

    @boomzilla considering it’s Raymond, the difference is probably that he actually thought about it and has a reason for it, while I have not. :mlp_shrug:

    It makes sense in the sense that a commit points back to its parent(s). It's confusing in the sense that I tend to think about it more as a flow through time.



  • @boomzilla I think we all intuitively see as flow through time. I can never ever understand the other approach cos the dissonance between what it means and what I think it should mean never goes away.



  • If Git Flow is a thing then...

    My mind is a raging torrent, flooded with rivulets of thought cascading into a waterfall of creative alternatives.


  • ♿ (Parody)

    @Arantor said in Git is amazing! Or Not!!:

    If Git Flow is a thing then...

    I will let the hate flow through me and when it is gone only I will remain.





  • @Arantor Wrong movie.



  • @Bulb I did get the reference despite the fact it's not just substituting fear/hate in the quote.

    At least, the book version I remember...

    I must not fear. Fear is the mind-killer. Fear is the little-death that brings total obliteration. I will face my fear. I will permit it to pass over me and through me. And when it has gone past I will turn the inner eye to see its path. Where the fear has gone there will be nothing. Only I will remain.


  • Fake News

    @Bulb said in Git is amazing! Or Not!!:

    And if that developer does changes on separate branches, they'll expect the pull to be “fast forward” and it won't be, which will tip them off that a rewrite happened. And then they'll push and that will fix the problem.
    ...
    The next person pushing to the branch will, if they don't expect this, merge the old and new history, getting the old history back in.

    And this "automatic merging" is why I nearly always invoke an alias for git pull --ff-only instead of plain git pull: it will fetch changes but stop as soon as it notices that things have diverged. One of those cases in git where the option-less command might not always be the "neatest".

    Of course there are adventurous people who will tell you to always run git pull --rebase because that's what they would be doing shortly after anyway, but I like to be at least warned that a rebase might get started (just in case it turns out there will be merge conflicts).


  • I survived the hour long Uno hand

    @JBert said in Git is amazing! Or Not!!:

    @Bulb said in Git is amazing! Or Not!!:

    And if that developer does changes on separate branches, they'll expect the pull to be “fast forward” and it won't be, which will tip them off that a rewrite happened. And then they'll push and that will fix the problem.
    ...
    The next person pushing to the branch will, if they don't expect this, merge the old and new history, getting the old history back in.

    And this "automatic merging" is why I nearly always invoke an alias for git pull --ff-only instead of plain git pull: it will fetch changes but stop as soon as it notices that things have diverged. One of those cases in git where the option-less command might not always be the "neatest".

    Of course there are adventurous people who will tell you to always run git pull --rebase because that's what they would be doing shortly after anyway, but I like to be at least warned that a rebase might get started (just in case it turns out there will be merge conflicts).

    C'mon, if you're not adventurous, why are you even in coding in the first place? 🍹


  • Considered Harmful

    @TimeBandit said in Git is amazing! Or Not!!:

    @stillwater said in Git is amazing! Or Not!!:

    wtf am I even looking at!

    Sounds like something @Gribnit would write 🤔

    He's just saying not to worry about your refs - unless of course you need to.


  • Considered Harmful

    @boomzilla said in Git is amazing! Or Not!!:

    time

    TDEMSYR


  • ♿ (Parody)


  • BINNED

    I prefer commits expressed as a complex function of frequency.


  • Considered Harmful

    @kazitor said in Git is amazing! Or Not!!:

    I prefer commits expressed as a complex function of frequency.

    I mean, this does generalize the problem of adding noise resistance.


  • Considered Harmful

    @dkf said in Git is amazing! Or Not!!:

    always do a force-commit. When I discovered that that was happening, I actually shouted at them on a project main telecon

    I'm assuming they were remote, since stabbing someone hardly makes any noise.


  • Considered Harmful

    @stillwater said in Git is amazing! Or Not!!:

    what I think it should mean

    Discard this concept entirely.



  • @izzion said in Git is amazing! Or Not!!:

    an alias for git pull --ff-only instead of plain git pull

    … or you could git config pull.ff only to make --ff-only to be the default.



  • @JBert said in Git is amazing! Or Not!!:

    Of course there are adventurous people who will tell you to always run git pull --rebase because that's what they would be doing shortly after anyway, but I like to be at least warned that a rebase might get started (just in case it turns out there will be merge conflicts).

    I am one of those people, and I don't find that adventurous at all. If there is any conflict, one git rebase --abort gets me back when I started and I can do something careful.

    Then again, I never ever work on branches shared with ten different people, so the chance to get undetected "semantic conflict" is negligible.


  • Fake News

    @Bulb said in Git is amazing! Or Not!!:

    @izzion said in Git is amazing! Or Not!!:

    an alias for git pull --ff-only instead of plain git pull

    … or you could git config pull.ff only to make --ff-only to be the default.

    Huh, that got added after I formed the habbit of always specifying that option or using an alias. Seems it has been there since 2014 when Git 2.0 was released, so that's good to know, thanks.



  • A colleague when solving a merge conflict overwrote the latest version which had a piece of code when absent caused a build error. I go digging and find this out, call my colleague and show him this and he goes "hey that was not the piece of code I wrote" and I go "I know. That was someone else's code and when you solved your merge conflict you did something like 'take local' and blindly overwrote the code on the server". I repeat the same explanation again and after a couple times he goes "Man this is so confusing. I'm sorry can you fix this". lolwtffml I helped him out but wtf.



  • @Zecc said in Git is amazing! Or Not!!:

    @Arantor said in Git is amazing! Or Not!!:

    As for having a hard time with Git, it mostly depends how dirty you get - if you just have commits and simple branching, it's not so bad. But the moment you start doing anything about manipulating branches (cherry picks, rebases, intentionally detached HEADs) things get messy fast if you're not fluent with what you're actually attempting to do.

    This.

    Also, Raymond Chen's series about avoiding cherry-picks spoke to me. Why I was like "aaAAH! A talking series of posts!"

    So, I'm reading through that, and at about the third post, I realized that the entire series is pretty irrelevant to me, because the code I'm working in right now uses squash-merges for PRs. The fun part about this is that once you squash-merge a feature branch, trying to merge it into master again after another commit tends to give you a bunch of merge conflicts, so my strategy is always to throw away a branch the moment it's merged. "Periodically merging a branch" is not a thing that happens in my world, ever.

    (When things get weird, I always have to go for the xkcd git technique: save my changes elsewhere, create a new branch, copy/overwrite files into new branch.)



  • @stillwater said in Git is amazing! Or Not!!:

    A colleague when solving a merge conflict overwrote the latest version which had a piece of code when absent caused a build error. I go digging and find this out, call my colleague and show him this and he goes "hey that was not the piece of code I wrote" and I go "I know. That was someone else's code and when you solved your merge conflict you did something like 'take local' and blindly overwrote the code on the server". I repeat the same explanation again and after a couple times he goes "Man this is so confusing. I'm sorry can you fix this". lolwtffml I helped him out but wtf.

    Yup, happens occasionally. Good explanations of what the merge algorithm is actually about are impossible to find for most people and for some it simply always remains a mystery.

    @PotatoEngineer said in Git is amazing! Or Not!!:

    So, I'm reading through that, and at about the third post, I realized that the entire series is pretty irrelevant to me, because the code I'm working in right now uses squash-merges for PRs.

    I consider that :doing_it_wrong:, but some people like it. I'm guessing they are the ones who don't understand merges so they do this to get a nice linear history, oblivious that not only do the merges still happen and this just makes it harder to go back and fix them up in case it's needed.

    The fun part about this is that once you squash-merge a feature branch, trying to merge it into master again after another commit tends to give you a bunch of merge conflicts, so my strategy is always to throw away a branch the moment it's merged. "Periodically merging a branch" is not a thing that happens in my world, ever.

    I also delete the branch once merged, though I always use no-ff merge for pull requests.

    Note: the no-ff merge commit and squash-merge commit are done the same way, and have the same content, except the second parent reference is removed for the squash commit.

    But that still leaves repeatedly merging master into the feature branch. My feature branches often exist long enough that I need the other changes there.

    Well, in my case it is pull --rebase unless I am working on the feature branch with other people, with a good measure of git rebase --interactive thrown in, but that's a rather minor detail.

    (When things get weird, I always have to go for the xkcd git technique: save my changes elsewhere, create a new branch, copy/overwrite files into new branch.)

    By that you are discarding a lot of useful information. Because while git saves states, to put stuff together you should think in terms of changes.

    This is where the revision range specifiers come in extremely handy: gitk origin/master.. to see my changes to be merged to master, gitk ..origin/master to see changes on master I did not merge yet, gitk ...origin/master to see both, git diff origin/master... to see what a merge to master will look like, git diff ...origin/master to see what a merge from master will look like. Note that if there is nothing on one side of the dots, HEAD is implied; you can compare arbitrary revisions that way. Just mind that for pair of revisions (diff) .. means between the two revisions and ... means from MRCA, while for range (git log, gitk) .. means in the second and not the first—so from MRCA to the second similar to the ... in diff—while ... means everything that isn't in both—so from MRCA to both.

    … I almost never look at history of a branch. It is uselessly verbose. But looking at ‘commits this branch has on top of the other one’ is massively useful.



  • @Bulb said in Git is amazing! Or Not!!:

    But that still leaves repeatedly merging master into the feature branch. My feature branches often exist long enough that I need the other changes there.

    Oh, I absolutely merge from master into my feature branch. Just not the other way; either the entire feature branch gets merged into master in a squash commit, or the feature branch remains separate. (Emergency partial-fixes from a feature branch are really rare; I use the xkcd method there when necessary, with a lot more checking.)

    Well, in my case it is pull --rebase unless I am working on the feature branch with other people, with a good measure of git rebase --interactive thrown in, but that's a rather minor detail.

    My team started with rebases, but we switched to merges fairly early. Either way, you tend to get merge conflicts if both master and feature branch are fiddling with the same things.

    (When things get weird, I always have to go for the xkcd git technique: save my changes elsewhere, create a new branch, copy/overwrite files into new branch.)

    By that you are discarding a lot of useful information. Because while git saves states, to put stuff together you should think in terms of changes.

    By "weird" I mean "I have typed entirely the wrong commands, in a way that git repositories are prone to." At that point, I have acquired another 50 changes (usually by being careless about which branch I'm in and which branch I'm merging from, modulo some fun with uncommitted changes), and going through the list will take too long. I usually just do a git reset --hard to wherever my "last known good" state is, and go on from there.

    This is where the revision range specifiers come in extremely handy: gitk origin/master.. to see my changes to be merged to master, gitk ..origin/master to see changes on master I did not merge yet, gitk ...origin/master to see both, git diff origin/master... to see what a merge to master will look like, git diff ...origin/master to see what a merge from master will look like. Note that if there is nothing on one side of the dots, HEAD is implied; you can compare arbitrary revisions that way. Just mind that for pair of revisions (diff) .. means between the two revisions and ... means from MRCA, while for range (git log, gitk) .. means in the second and not the first—so from MRCA to the second similar to the ... in diff—while ... means everything that isn't in both—so from MRCA to both.

    It is extremely rare for me to get into a "weird" state and not know where I'm supposed to be. (I'm honestly not sure that it's ever happened.) So delving into git magic would only be useful for increasing my hoard of knowledge, rather than actually extracting myself from my predicaments. I suppose the squash-merging-from-master also makes my branch's history pretty simple: everything is either a merge (which must have come from master) or not-a-merge (which must be local).


  • Fake News

    @Bulb said in Git is amazing! Or Not!!:

    This is where the revision range specifiers come in extremely handy: gitk origin/master.. to see my changes to be merged to master, gitk ..origin/master to see changes on master I did not merge yet, gitk ...origin/master to see both, git diff origin/master... to see what a merge to master will look like, git diff ...origin/master to see what a merge from master will look like. Note that if there is nothing on one side of the dots, HEAD is implied; you can compare arbitrary revisions that way. Just mind that for pair of revisions (diff) .. means between the two revisions and ... means from MRCA, while for range (git log, gitk) .. means in the second and not the first—so from MRCA to the second similar to the ... in diff—while ... means everything that isn't in both—so from MRCA to both.

    I always forget about that syntax, and I also can't be bothered to type out branch names or switch order, so personally I would introduce a function / alias to do that for me:

    gitk "^$(git merge-base HEAD 'HEAD@{upstream}')" HEAD "HEAD@{upstream}"

    I actually have a similar shell alias/function but without excluding the common history: running it without arguments runs it for HEAD, giving it branch names will look those up instead.


  • Trolleybus Mechanic

    @stillwater I frequently do it if I'm adding trivial fixes or extra comments that are still in line with that commit. I don't unwind things in order to do it. If an unrelated commit is on top, I just make a new commit.

    Rarely I've done it after doing a hard reset as a way to throw away bad changes for a branch and get it caught back up to master.



  • This thread tells me that more people should have ClearCase inflicted upon them.


Log in to reply