Reputation: 35795
I have a file config.txt
in a given branch of a remote git repository.
Now I have a new version of that file (outside of any git repository, just a generated file) and want to commit it. For that, I need to write a shell script. What would be a good approach for that?
Do I need a local copy of the whole git repository? Or can it be done more directly?
Upvotes: 0
Views: 63
Reputation: 487993
First, remember that what Git really cares about are commits, and each commit is a full snapshot of all files. Moreover, each commit is made from whatever is in Git's index at the time you run git commit
. A git checkout
of a given commit fills the index from that commit, while also populating a work-tree with those files (in their ordinary form) so that you can see them.
Thus, if you want to make a new commit in which all files are the same as the previous commit except for config.txt
which should be replaced wholesale, you must:
config.txt
file in the index with the new contentsThe easy way to do that is to have the repository—or a clone of it—with a work-tree in location W (some directory/folder on your machine), in a "clean" state (so that git status
in W will say nothing to commit, working tree clean
). Then, in W, do:
git checkout branchname
which fills the index and work-tree of the repository in W. Then you can:
cp <path> config.txt
(where path
is the new generated confing), then:
git add config.txt
git commit -m "replace config.txt with new generated config"
If this repository is copied from some other Git repository elsewhere, you can now git push
the new commit to that other Git repository, and ask that Git repository to update its branch name to incorporate the new commit (which of course remembers its predecessor as usual).
Remember that this git push
may be racing against other users who are also updating that other Git, so it might fail if they updated the other Git's branch. In this case, you should probably mostly start over: update the local clone to pick up their new commit(s), hard-reset the one branch, copy the generated config.txt
, add, commit, and push. If that fails, restart yet again, until it succeeds.
(If there is a constraint by which the named branch should not have been updated by anyone else in this time period, you can skip the restart-and-retry loop, and simply report any failure for some human to diagnose.)
Do I need a local copy of the whole git repository?
You need enough of the Git repository to be able to check out the target branch (thus populating index and work-tree), replace the one file, git add
, git commit
, and git push
. How much is "enough"? Well, all you really need is the one current branch tip of that one named branch, so a shallow clone of depth 1 suffices. Hence if you don't have a repository at all, you can start the retry loop with:
git clone --depth 1 --single-branch -b <branch-name> <url> [<local-dir>]
to obtain this shallow clone in whatever directory. Should the push fail and you wish to have a retry loop, you can just git fetch
in this now-existing shallow clone to update origin/branch-name
, then use git reset --hard origin/branch-name
to reset for the next add/commit/push attempt. You should probably then remove this repository as shallow single-branch clones are something of a sand-trap / tar-pit / whatever your favorite term might be for other humans who stumble across them and aren't prepared for all the sharp pointy bits that shallow and single-branch repositories present.
Or can it be done more directly?
Rather than doing all of this on a client, in a clone, and git push
ing to the server Git, you could send the newly generated config.txt
to the server—to a location outside its clone, which is probably --bare
anyway—by some non-Git method (such as scp
or rsync
) and run something directly on the server. This, of course, assumes you have that kind of access to the server.
If you have a bare repository—which is one with no work-tree—you can still make a new commit, because even a bare repository has an index. You simply need to read the existing branch-tip commit into the index:
git read-tree <branch-name>
then replace that one file in the index using git update-index
(with --stdin
or --cacheinfo
), but to do so you'll need to use git hash-object -w
to create the blob object from the desired content. Then, having updated the index, use git write-tree
to write a new tree object, then use git commit-tree
to build a commit from the resulting tree object, and last, use git update-ref
to update the branch name to point to the new commit. If the branch might be updated via git push
, you'll still need a retry loop, or some method of locking the branch against git push
. Any locking method is up to you to invent, perhaps using hooks.
Upvotes: 1